当前位置: 首页 > news >正文

SpringBoot 缓存注解:@Cacheable/@CacheEvict 使用

在 SpringBoot 里做缓存,除了手动操作 RedisTem)plate,更优雅、更常用的方式就是Spring 自带的缓存注解
不用写重复的缓存逻辑,只需要在方法上加个注解,就能自动实现缓存读写,代码干净又好维护。

本篇文章就来讲讲最核心的两个注解:

  • @Cacheable:查询时自动缓存

  • @CacheEvict:更新/删除时自动清理缓存


一、为什么要用缓存注解?

  • • 无侵入:业务代码和缓存代码分离,不污染逻辑

  • • 极简开发:一行注解替代一堆get/set缓存代码

  • • 统一管理:过期时间、缓存名称、key 规则集中配置

  • • 适配多种缓存:Redis、Caffeine、内存缓存都支持

适合:

  • • 查询多、修改少的接口

  • • 商品详情、用户信息、字典数据、配置列表

  • • 不想写重复缓存模板代码的场景


二、基础环境准备

1. 引入依赖

<!-- Redis + 缓存 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>

2. 启动类开启缓存

@SpringBootApplication @EnableCaching // 必须加 public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

3. application.yml 缓存配置

spring: cache: type: redis redis: time-to-live: 3600000 # 默认1小时过期 cache-null-values: false # 不缓存null

三、核心注解 1:@Cacheable(查询 + 缓存)

作用:
第一次执行方法 → 查库 → 结果存入缓存
后续请求 → 直接走缓存,不执行方法体

1. 基础用法

@Cacheable(value = "userCache", key = "#userId") public User getUserById(Long userId) { return userMapper.selectById(userId); }
  • value / cacheNames:缓存名(区分不同业务)

  • key:缓存 key,支持 SpEL 表达式

最终 Redis key:

userCache::1001

2. 常用 key 写法

// 单个参数 @Cacheable(value = "user", key = "#id") // 对象参数取id @Cacheable(value = "user", key = "#user.id") // 方法名当key @Cacheable(value = "dict", key = "#root.methodName") // 组合key @Cacheable(value = "order", key = "'uid:'+#userId+':type:'+#type")

3. 条件缓存(满足才缓存)

// 只缓存成年用户 @Cacheable(value = "user", key = "#userId", condition = "#result.age >= 18")
// 结果不为null才缓存 @Cacheable(value = "user", key = "#userId", unless = "#result == null")

四、核心注解 2:@CacheEvict(删除缓存)

作用:数据更新/删除后,清理旧缓存,保证数据一致。

1. 根据 key 删除

@CacheEvict(value = "userCache", key = "#user.id") public void updateUser(User user) { userMapper.updateById(user); }

2. 删除整个缓存名下所有 key

@CacheEvict(value = "userCache", allEntries = true) public void refreshAllUser() { }

3. 方法执行前删除

@CacheEvict(value = "user", key = "#userId", beforeInvocation = true)

五、另外两个常用注解

1. @CachePut

强制更新缓存,每次都会执行方法体,适合实时更新缓存。

@CachePut(value = "user", key = "#user.id") public User updateUser(User user) { userMapper.updateById(user); return user; }

2. @Caching

组合多个缓存操作:

@Caching( evict = { @CacheEvict(value = "user", key = "#userId"), @CacheEvict(value = "userOrder", key = "#userId") } ) public void deleteUser(Long userId) { }

六、模拟场景

场景 1:用户详情(典型查询缓存)

@Cacheable(value = "userInfo", key = "#userId", unless = "#result == null") public User getUser(Long userId) { return userMapper.selectById(userId); }

场景 2:修改用户 → 清理缓存

@CacheEvict(value = "userInfo", key = "#user.id") public void updateUser(User user) { userMapper.updateById(user); }

场景 3:删除用户 → 清理缓存

@CacheEvict(value = "userInfo", key = "#userId") public void deleteUser(Long userId) { userMapper.deleteById(userId); }

场景 4:商品列表缓存

@Cacheable(value = "productList", key = "#categoryId") public List<Product> getProductList(Integer categoryId) { return productMapper.selectByCategory(categoryId); }

场景 5:批量刷新商品缓存

@CacheEvict(value = "productList", allEntries = true) public void refreshProduct() { }

场景 6:字典/配置(几乎不变,长期缓存)

@Cacheable(value = "dictCache", key = "#dictType") public List<Dict> getDict(String dictType) { return dictMapper.selectByType(dictType); }

七、注意事项

  1. 1.同类方法内调用,注解失效
    因为走了代理,同类内部方法调用不经过 AOP。
    解决:抽取到独立 Service 或自己注入自己。

  2. 2.缓存 key 冲突
    不同业务一定要用不同value/cacheNames

  3. 3.缓存与数据库不一致
    增删改必须配合@CacheEvict@CachePut

  4. 4.大数据量列表缓存
    列表缓存容易占内存,建议设置更短过期时间 + 分页缓存。

  5. 5.null 值被缓存
    unless = "#result == null"避免。

  6. 6.事务与缓存顺序问题
    建议事务提交后再清缓存,否则会出现“脏缓存”。


八、@Cacheable vs @CachePut vs @CacheEvict 总结

注解

作用

执行方法体

典型场景

@Cacheable

查 + 缓存

缓存不存在才执行

查询接口

@CachePut

强制更新缓存

每次都执行

实时同步

@CacheEvict

删除缓存

每次都执行

增删改


九、总结

SpringBoot 缓存注解是后端最实用的简化技巧之一

  • • 查询用@Cacheable

  • • 更新删除用@CacheEvict

  • • 实时同步用@CachePut


你在项目里更喜欢用注解还是手动操作 Redis?有没有遇到过缓存不一致、注解不生效的坑?

欢迎在评论区留言交流,关注我!

http://www.jsqmd.com/news/590561/

相关文章:

  • 解锁浏览器潜能:Greasy Fork用户脚本平台完全指南
  • 小白友好!通义千问3-4B手机端部署常见问题与解决大全
  • 智能汽车时代必看:电子电气架构如何支撑自动驾驶升级?(含SOA架构对比)
  • 在RT-Thread Nano上玩转Agile Modbus主机:一个完整的数据读写轮询任务实现
  • 2026年常州ERP公司哪家比较好? - 品牌排行榜
  • 使用LaTeX撰写基于YOLOv12的学术论文:图表与算法排版最佳实践
  • 突破网页媒体限制:3分钟掌握资源嗅探技术,轻松下载在线音视频
  • Heygem数字人视频生成系统批量版实测:5分钟快速上手,批量制作口型同步视频
  • 告别龟速下载!Win10/Win11下用WSL2+国内镜像源5分钟搞定CDO安装
  • 2026年常州有哪些ERP企业?本地企业数字化转型选择参考 - 品牌排行榜
  • 从cp到scp:在复旦微FMQL45T900上高效管理文件的完整指南
  • nli-distilroberta-base行业落地:药品说明书与患者用药指导的中立性风险提示
  • MedGemma-X在基层医院的应用:快速辅助诊断,解决放射科医生不足难题
  • 深入解析打流技术:从DDoS攻击到网络性能测试
  • Apatch内核模块开发避坑指南:从零实现syscall监控与ARM64栈回溯
  • NoC流控制实战:从Bufferless到Virtual-Channel的5种策略对比与选型指南
  • Youtu-VL-4B-Instruct商业价值:降低90%人工图文处理成本的实测数据
  • 2026年推荐适合烫发的护发精油,告别干枯毛躁有方法 - 品牌排行榜
  • 单端与差分信号:原理、对比与工程实践
  • 泰迪杯B题实战:用LightGBM搞定产品需求预测的5个关键步骤
  • 从Finalshell换到Xshell,我的真实体验与完整迁移配置指南(附Xftp对比WinSCP)
  • 千问3.5-9B代码审查专家:Java/Python项目自动化代码质量分析
  • 2026护发精油品牌推荐:这些口碑好物值得关注 - 品牌排行榜
  • 科哥Face Fusion场景应用:社交娱乐、内容创作、数字人,玩法全解析
  • MX1508电机驱动库详解:衰减模式与双路H桥控制
  • Llama-3.2V-11B-cot实战:基于Vue3的前端智能对话界面开发
  • Stable-Diffusion-V1-5 创意写作辅助:为小说生成角色与场景设定图
  • 给理工科研究生的保姆级指南:SCI、EI、CSCD这些数据库到底怎么用?
  • SpringCloud Config客户端配置加载失败?解析bootstrap.yml的关键作用
  • 从上传到结果:OpenDataLab MinerU智能文档理解完整使用流程