原来 Spring Cache 还能这么用?这些骚操作让你的开发效率翻倍!

Spring Cache 不仅仅是个简单的缓存注解工具,掌握这些隐藏技巧,能让你的代码性能飙升,代码量减少50%!

一、基础用法:三剑客闯江湖

@Service
public class ProductService {
    
    // 1. @Cacheable:优先读缓存,无缓存时执行方法并缓存结果
    @Cacheable(value = "products", key = "#id", unless = "#result.price > 1000")
    public Product getProductById(Long id) {
        // 模拟数据库查询
        return productRepository.findById(id);
    }
    
    // 2. @CachePut:更新缓存(始终执行方法)
    @CachePut(value = "products", key = "#product.id")
    public Product updateProduct(Product product) {
        return productRepository.save(product);
    }
    
    // 3. @CacheEvict:删除缓存
    @CacheEvict(value = "products", key = "#id", allEntries = false)
    public void deleteProduct(Long id) {
        productRepository.deleteById(id);
    }
}

核心参数解析:

参数

作用

应用场景

value

指定缓存名称

多业务隔离(如:users, products)

key

自定义缓存键(SpEL表达式)

细粒度控制(如:#user.id

unless

条件阻止缓存(SpEL表达式)

过滤特定数据(如:价格>1000不缓存)

allEntries

是否清空整个缓存区域

批量删除(如:重置整个分类)


二、进阶骚操作:让缓存飞起来

1. 多级缓存策略(本地缓存+Redis)

# application.yml
spring:
  cache:
    type: composite
    caffeinespec: maximumSize=500,expireAfterWrite=10s
    redis:
      time-to-live: 30m
@Configuration
public class CacheConfig {
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        return new CompositeCacheManager(
            new CaffeineCacheManager("local_cache"),
            RedisCacheManager.create(factory)
        );
    }
}

2. 防止缓存击穿(同步加载)

@Cacheable(value = "products", key = "#id", sync = true)
public Product getProductWithSync(Long id) {
    // 高并发时只有一个线程执行加载
    return heavyQueryFromDB(id);
}

3. 动态TTL(不同数据不同过期时间)

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
    return RedisCacheManager.builder(factory)
        .withInitialCacheConfigurations(Map.of(
            "short_ttl", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)),
            "long_ttl", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1))
        )).build();
}

三、避坑指南:这些雷区千万别踩!

1. 自调用失效问题

// ❌ 错误:类内部调用不走代理
public void updateProductPrice(Long id) {
    // 内部调用不会触发缓存清除
    this.clearCache(id); 
}

@CacheEvict(value="products", key="#id")
private void clearCache(Long id) {}

// ✅ 正确:通过AopContext解决
@EnableAspectJAutoProxy(exposeProxy = true)
public void updateProductPrice(Long id) {
    ((ProductService) AopContext.currentProxy()).clearCache(id);
}

2. 缓存穿透防护

// 空值缓存策略
@Cacheable(value="users", key="#id", unless = "#result == null")
public User getUser(Long id) {
    User user = userDao.findById(id);
    if(user == null) {
        // 缓存空对象(设置短TTL)
        return new NullUser(); 
    }
    return user;
}

3. 事务与缓存顺序

// ❌ 缓存更新可能发生在事务回滚前
@Transactional
@CacheEvict(value="products", key="#id")
public void updateWithRisk(Long id) {
    // 数据库操作(可能失败回滚)
}

// ✅ 正确:事务提交后清除缓存
@CacheEvict(value="products", key="#id", afterInvocation = true)
@Transactional
public void safeUpdate(Long id) {
    // 业务逻辑
}

四、性能核弹:异步缓存模式

// 1. 配置异步缓存
@Bean
public CacheManager cacheManager() {
    CaffeineCacheManager manager = new CaffeineCacheManager();
    manager.setAsyncCacheLoader(new AsyncCacheLoader<Object, Object>() {
        @Override
        public CompletableFuture<Object> load(Object key, Executor executor) {
            return CompletableFuture.supplyAsync(() -> {
                // 异步加载数据
                return heavyLoadOperation(key);
            }, executor);
        }
    });
    return manager;
}

// 2. 异步调用
@Cacheable(value = "async_cache", key = "#id")
public CompletableFuture<Product> getProductAsync(Long id) {
    // 立即返回Future对象
}

实战场景:电商库存缓存方案

@Service
public class InventoryService {
    // 组合注解:先删缓存再更新DB
    @Caching(evict = {
        @CacheEvict(value = "stock", key = "#skuId"),
        @CacheEvict(value = "hot_items", allEntries = true)
    })
    @Transactional
    public void updateStock(String skuId, int delta) {
        inventoryDao.updateStock(skuId, delta);
    }

    // 热点商品特殊处理:本地缓存+Redis二级缓存
    @Cacheable(value = "stock", key = "#skuId", cacheManager = "compositeCacheManager")
    public Integer getStock(String skuId) {
        return inventoryDao.getStock(skuId);
    }
}

性能对比:

方案

QPS

平均响应

直接查DB

120

45ms

普通Redis缓存

3500

8ms

本地+Redis二级

8500

2ms


结语:缓存的艺术

Spring Cache 的精髓在于平衡

  1. unless过滤不必要的数据
  2. sync保护数据库
  3. Composite实现多级加速
  4. afterInvocation确保数据一致性

缓存不是银弹,但用对场景能让性能提升百倍!记住:缓存命中率 > 80% 才是有效缓存,否则立即检查你的Key设计!

© 版权声明

相关文章

8 条评论

您必须登录才能参与评论!
立即登录
  • 头像
    小二七 读者

    缓存策略很实用👍

    无记录
  • 头像
    英不英文 读者

    避坑指南很关键

    无记录
  • 头像
    红霞小屋世茂二楼 读者

    这些操作能提效呢👏

    无记录
  • 头像
    一念蘑菇 投稿者

    收藏了,感谢分享

    无记录
  • 头像
    专业老师 读者

    这和直接写代码还有啥区别

    无记录
  • 头像
    鬼哥 读者

    缓存技巧用对了,真是性能提升器!

    无记录
  • 头像
    默然微语 读者

    缓存的世界原来这么大

    无记录
  • 头像
    -kimma 投稿者

    Spring Cache用法真多😃

    无记录