精通Spring缓存注解@Cacheable:从原理到实战的完整指南
文章导读目录
- @Cacheable注解的核心概念
- 什么是缓存?为什么需要缓存?
- @Cacheable的工作原理与执行流程
- @Cacheable的详细使用方式
- 基础配置:value、key、condition与unless
- 条件缓存与动态key的实战技巧
- 自定义缓存管理器与缓存名称空间
- 与@CachePut、@CacheEvict的配合策略
- 更新缓存的最佳实践
- 清除缓存的时机与方案
- 常见问题与性能优化
- 缓存穿透、雪崩、击穿的应对
- 方法内部调用缓存失效的解决方案
- 真实企业级案例解析
用户信息缓存 + 商品详情缓存

- Q&A 高频问答环节
@Cacheable注解的核心概念
什么是缓存?为什么需要缓存?
缓存是存储在内存中的临时数据副本,用于减少对数据库或外部服务的重复访问,在高并发场景下,缓存能将响应时间从几十毫秒降低到微秒级,同时显著降低数据库负载,Spring缓存框架抽象了不同缓存实现(Redis、EhCache、Caffeine等),而@Cacheable是最常用的注解之一。
@Cacheable的工作原理与执行流程
当一个方法被注解为@Cacheable后,调用该方法前,Spring会先检查缓存中是否存在匹配的key:
- 如果缓存命中,直接返回缓存结果(方法不执行)。
- 如果缓存未命中,执行方法体,然后将返回结果存入缓存。
伪代码逻辑:
// 伪代码展示内部流程
if (cache.containsKey(key)) {
return cache.get(key);
} else {
result = method.execute(params);
cache.put(key, result);
return result;
}
@Cacheable的详细使用方式
基础配置
@Cacheable(value = "users", key = "#userId", unless = "#result == null")
public User getUserById(Long userId) {
// 查询数据库
return userRepository.findById(userId).orElse(null);
}
- value/cacheNames:缓存名称,对应缓存分区(如Redis的key前缀)。
- key:支持SpEL表达式,如
#p0、#userId、#root.methodName。 - condition:满足条件才缓存(布尔表达式)。
- unless:满足条件不缓存(通常用于null值排除)。
动态key与条件缓存实战
@Cacheable(value = "products",
key = "#productId + '_' + T(java.util.Locale).getDefault().getLanguage()",
condition = "#productId > 1000")
public Product getProduct(Long productId) {
return productService.loadById(productId);
}
- 动态key:使用SpEL拼接实现多参数组合。
- condition:只有
productId > 1000时才启用缓存。
自定义缓存管理器
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(500)
.expireAfterWrite(10, TimeUnit.MINUTES));
return manager;
}
}
通过自定义CacheManager,可精细控制缓存的过期策略、最大容量等。
与@CachePut、@CacheEvict的配合策略
@CachePut:更新缓存而不影响方法执行
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
每次都会执行方法,并将返回值更新到缓存中,适用于数据修改后的缓存同步。
@CacheEvict:清除缓存的最佳时机
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
allEntries = true:清空整个缓存分区。beforeInvocation = true:在方法执行前清除(可用于异常场景)。- 常用策略:更新数据时先
@CachePut更新,删除时用@CacheEvict。
常见问题与性能优化
缓存穿透、雪崩、击沉的应对
- 穿透:查询不存在的数据,方案:缓存空值并使用
unless = "#result == null"。 - 雪崩:大量缓存同时过期,方案:设置随机过期时间(如
expireAfterWrite(300 + new Random().nextInt(100), TimeUnit.SECONDS))。 - 击穿:热点数据过期瞬间高并发,方案:使用
@Cacheable(sync = true)开启同步锁。
方法内部调用缓存失效
@Service
public class ServiceA {
@Cacheable("data")
public String getData() { ... }
public void caller() {
// 直接调用时,缓存失效
String result = this.getData(); // ❌ 不走缓存
}
}
解决方案:
- 注入自身代理对象
@Autowired private ServiceA self;,调用self.getData()。 - 或将
@Cacheable抽取到单独服务类。
真实企业级案例解析
案例:用户信息缓存 + 商品详情缓存
@Service
public class UserInfoService {
// 用户信息缓存:key为userId,除非结果为null
@Cacheable(value = "user:info", key = "#userId", unless = "#result == null")
public UserInfo getUserInfo(Long userId) {
return userDao.findById(userId);
}
// 商品缓存:支持多语言
@Cacheable(value = "product:detail",
key = "#productId + '_' + #locale.toString()",
condition = "#productId != null && #locale != null")
public ProductDetail getProductDetail(Long productId, Locale locale) {
return productDao.findDetail(productId, locale);
}
}
该案例在电商系统中十分常见,用户信息与商品详情是缓存命中率极高的热点数据。
Q&A 高频问答环节
Q1:@Cacheable的key为空会怎样?
A:如果key计算为null,Spring会抛出异常,建议在SpEL中增加判空:key = "#userId ?: 'default'"。
Q2:多个@Cacheable注解可以叠加吗?
A:Spring允许使用@Caching组合多个缓存操作:
@Caching(cacheable = {
@Cacheable("users"),
@Cacheable("admins")
})
public User getUser(Long id) { ... }
Q3:如何为不同缓存设置不同的过期时间?
A:在自定义CacheManager中使用RedisCacheConfiguration,为每个cacheNames单独配置:
Map<String, RedisCacheConfiguration> configs = new HashMap<>();
configs.put("users", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)));
configs.put("products", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)));
Q4:@Cacheable能否与事务结合使用?
A:可以,但要注意事务提交时机,缓存通常应在事务成功提交后更新,避免回滚导致数据不一致,可使用@Transactional(propagation = Propagation.REQUIRED)配合使用。
Q5:如何监控缓存命中率?
A:可通过AOP自定义注解统计调用次数,或集成actuator暴露缓存统计端点(如/actuator/caches)。