@Cacheable 和 @CacheEvict 注解的详细使用说明及参数解析,结合 Spring Cache 的核心功能和实际开发场景
一、@Cacheable 注解详解
1. 核心作用
@Cacheable
用于标记方法的返回值需要被缓存。
执行逻辑:
- 方法调用前检查缓存:若缓存存在且有效,直接返回缓存值;否则执行方法并将结果存入缓存。
2. 关键参数
参数名 | 作用 | 示例 |
---|---|---|
value/cacheNames | 必填,指定缓存名称(命名空间),可配置多个缓存。 | @Cacheable(value = "users", key = "#id") |
key | 指定缓存键(支持 SpEL 表达式),默认使用方法参数生成键。 | key = "#id" 或 key = "#user.name" |
condition | 方法执行前判断条件,满足时才缓存结果(SpEL 表达式)。 | condition = "#id > 0" |
unless | 方法执行后判断条件,满足时 不 缓存结果(SpEL 表达式)。 | unless = "#result == null" |
sync | 是否同步执行缓存(防止并发重复计算),默认 false 。 | sync = true |
cacheManager | 指定使用的 CacheManager 实现(如 Redis、Caffeine)。 | cacheManager = "redisCacheManager" |
cacheResolver | 自定义缓存解析器(高级用法,用于动态选择缓存)。 | cacheResolver = "customCacheResolver" |
3. 使用场景
- 查询操作:如根据 ID 查询用户、商品信息。
- 静态数据:如配置项、字典表数据,更新频率低。
- 高频读取:减少数据库或远程调用压力。
4. 示例代码
@Cacheable(value = "users", key = "#id", condition = "#id > 0", unless = "#result == null"
)
public User getUserById(Long id) {// 查询数据库逻辑return userRepository.findById(id);
}
解释:
value = "users"
:缓存名称为users
。key = "#id"
:缓存键为参数id
。condition = "#id > 0"
:仅当id > 0
时才缓存结果。unless = "#result == null"
:若查询结果为null
,不缓存(防止空值占用缓存空间)。
二、@CacheEvict 注解详解
1. 核心作用
@CacheEvict
用于清除缓存条目。
执行逻辑:
- 方法执行后(或前)删除指定缓存条目或清空整个缓存区域。
2. 关键参数
参数名 | 作用 | 示例 |
---|---|---|
value/cacheNames | 必填,指定要清除的缓存名称(命名空间)。 | @CacheEvict(value = "users", key = "#id") |
key | 指定要清除的缓存键(支持 SpEL 表达式)。 | key = "#id" 或 key = "#user.name" |
allEntries | 是否清空整个缓存区域(默认 false ,仅清除单个键)。 | allEntries = true |
beforeInvocation | 是否在方法执行前清除缓存(默认 false ,方法执行后清除)。 | beforeInvocation = true |
condition | 方法执行前判断条件,满足时才清除缓存(SpEL 表达式)。 | condition = "#id > 0" |
3. 使用场景
- 删除操作:如删除用户后清除对应缓存。
- 更新操作:如修改用户信息后清除旧缓存。
- 批量操作:清空整个缓存区域(如更新配置后刷新所有缓存)。
4. 示例代码
4.1 清除单个缓存条目
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {// 删除数据库逻辑userRepository.deleteById(id);
}
解释:
value = "users"
:清除users
缓存区域。key = "#id"
:清除键为id
的缓存条目。
4.2 清空整个缓存区域
@CacheEvict(value = "users", allEntries = true)
public void refreshAllUsersCache() {// 刷新所有用户缓存逻辑userRepository.refresh();
}
解释:
allEntries = true
:清空users
缓存区域的所有条目。
4.3 在方法执行前清除缓存
@CacheEvict(value = "users", key = "#id", beforeInvocation = true
)
public User updateUser(User user) {// 更新数据库逻辑return userRepository.save(user);
}
解释:
beforeInvocation = true
:方法执行前清除缓存(适用于需先清除旧数据再更新的场景)。
三、@Cacheable 与 @CacheEvict 的对比
特性 | @Cacheable | @CacheEvict |
---|---|---|
作用 | 缓存方法返回值 | 清除缓存条目或区域 |
执行时机 | 方法执行前(命中缓存则跳过方法) | 方法执行后(或前,通过 beforeInvocation ) |
是否影响方法执行 | 可能跳过方法执行 | 始终执行方法,仅影响缓存状态 |
关键参数 | value , key , condition , unless | value , key , allEntries , beforeInvocation |
适用场景 | 查询操作、静态数据 | 删除/更新操作、缓存刷新 |
四、高级用法与注意事项
1. 条件控制:condition vs unless
condition
:方法执行前判断是否触发缓存逻辑。@Cacheable(value = "users", key = "#id", condition = "#id > 0") public User getUserById(Long id) {return userRepository.findById(id); }
unless
:方法执行后判断是否缓存结果。@Cacheable(value = "users", key = "#id", unless = "#result == null") public User getUserById(Long id) {return userRepository.findById(id); }
2. 缓存键生成策略
- 默认键生成:使用方法参数生成键(如
SimpleKey
)。 - 自定义键:通过 SpEL 表达式指定键(如
#id
、#user.name
)。 - 复杂对象:使用
#root.methodName
或#root.method
动态生成键。
3. 缓存一致性保障
- 删除操作后清除缓存:确保数据库与缓存数据一致。
- 更新操作后更新缓存:使用
@CachePut
强制刷新缓存(见下文)。
4. 异常处理
@CacheEvict
的beforeInvocation
:- 若
beforeInvocation = true
,方法抛出异常时仍会清除缓存(需谨慎)。
- 若
@Cacheable
的unless
:- 若方法抛出异常,不会缓存结果。
五、组合使用示例
1. 同时使用 @Cacheable 和 @CacheEvict
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {return userRepository.findById(id);
}@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {userRepository.deleteById(id);
}
2. 使用 @Caching 组合多个操作
@Caching(cacheable = @Cacheable(value = "users", key = "#id"),evict = @CacheEvict(value = "userRoles", key = "#id")
)
public User getUserWithRoles(Long id) {return userRepository.findUserWithRoles(id);
}
六、常见问题与解决方案
1. 缓存穿透(Cache Penetration)
- 问题:查询不存在的数据(如
id=invalid
),导致频繁访问数据库。 - 解决方案:
- 缓存空值:允许缓存
null
结果(unless = "#result == null"
改为true
)。 - 布隆过滤器:前置过滤非法请求。
- 缓存空值:允许缓存
2. 缓存雪崩(Cache Avalanche)
- 问题:大量缓存同时失效,导致数据库压力骤增。
- 解决方案:
- 随机过期时间:为缓存设置不同 TTL。
- 热点数据永不过期:对高频数据不设置过期时间。
3. 缓存击穿(Cache Breakdown)
- 问题:热点数据过期后,大量请求直接访问数据库。
- 解决方案:
- 互斥锁:在缓存失效时加锁,仅允许一个线程重建缓存。
- 永不过期 + 定期刷新:对热点数据设置永不过期,后台定期刷新。
七、总结
注解 | 核心用途 | 典型场景 |
---|---|---|
@Cacheable | 缓存查询结果 | 查询接口、静态数据 |
@CacheEvict | 清除缓存 | 删除/更新接口、缓存刷新 |
@CachePut | 强制更新缓存 | 修改数据后同步缓存 |
通过合理使用这些注解,开发者可以高效管理缓存,显著提升系统性能,同时避免缓存一致性问题。