Spring Cache 缓存更新与失效时机

Spring Cache 提供一层缓存抽象,可以用注解描述缓存读取和删除逻辑,底层由具体的 CacheManager 实现。它减少重复样板代码,但不会自动替应用解决缓存一致性、并发或 Redis 故障。

1. 读取缓存:@Cacheable

方法调用时,框架按缓存名和 Key 查找缓存。命中时可直接返回缓存值;未命中时执行方法,再按配置保存结果。Key 应能唯一表示方法参数或业务对象,避免不同租户或不同用户共享不该共享的结果。

2. 写入和删除:@CachePut、@CacheEvict

  • @CachePut 总会执行目标方法,并把返回结果写入缓存,适合方法结果可直接作为缓存值的场景。
  • @CacheEvict 删除匹配的缓存项,适合数据库更新后让后续读取重新加载。
  • allEntries = true 会清空指定缓存区域中的所有条目,影响范围较大,使用前应评估。

以菜品分类缓存为例,查询分类列表时可按门店 ID 缓存;新增、修改或删除分类及菜品时,要失效受影响的缓存键。缓存粒度应与数据更新范围一致。

3. 事务边界会影响失效时机

如果缓存删除发生在数据库事务提交之前,并发读可能在删除后、数据库提交前读到旧数据并重新写进缓存。事务最终提交后,这份旧缓存仍可能存在。因此重要更新要考虑在数据库成功提交后再失效,或使用事务同步、可靠事件和补偿机制。

注解的具体时机还会受配置和框架版本影响;beforeInvocation 等选项有不同语义。不能只看注解名字就假设它一定在事务提交后执行。

4. 代理调用和自调用

Spring Cache 通常通过代理拦截方法调用。一个类内部直接调用自己的缓存方法,可能绕过代理,使注解没有被执行。可将缓存边界拆到另一个 Spring Bean,或通过明确的缓存 API 调用。

5. 缓存 Key 与数据隔离

为 Key 设计稳定命名,例如 dish:list:{shopId}:{categoryId}。检查:

  • 是否包含影响结果的所有参数?
  • 不同用户、门店或租户之间是否隔离?
  • 删除数据时是否能定位对应缓存?
  • Key 中是否包含不应暴露的个人信息?

6. 小结

Spring Cache 负责把缓存操作映射到方法调用;数据一致性策略仍需由业务设计。先画出读、写、事务提交和失效的时间线,再决定注解边界和缓存粒度。