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

在 SpringBoot 里做缓存,除了手动操作 RedisTem)plate,更优雅、更常用的方式就是Spring 自带的缓存注解

不用写重复的缓存逻辑,只需要在方法上加个注解,就能自动实现缓存读写,代码干净又好维护。

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

  • @Cacheable:查询时自动缓存

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


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

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

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

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

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

适合:

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

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

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


二、基础环境准备

1. 引入依赖

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

2. 启动类开启缓存

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

3. application.yml 缓存配置

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

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

作用:

第一次执行方法 → 查库 → 结果存入缓存

后续请求 → 直接走缓存,不执行方法体

1. 基础用法

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

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

最终 Redis key:

go 复制代码
userCache::1001

2. 常用 key 写法

go 复制代码
// 单个参数
@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. 条件缓存(满足才缓存)

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

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

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

1. 根据 key 删除

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

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

go 复制代码
@CacheEvict(value = "userCache", allEntries = true)
public void refreshAllUser() {
}

3. 方法执行前删除

go 复制代码
@CacheEvict(value = "user", key = "#userId", beforeInvocation = true)

五、另外两个常用注解

1. @CachePut

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

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

2. @Caching

组合多个缓存操作:

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

六、模拟场景

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

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

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

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

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

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

场景 4:商品列表缓存

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

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

go 复制代码
@CacheEvict(value = "productList", allEntries = true)
public void refreshProduct() {
}

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

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

七、注意事项

    1. 同类方法内调用,注解失效

    因为走了代理,同类内部方法调用不经过 AOP。

    解决:抽取到独立 Service 或自己注入自己。

    1. 缓存 key 冲突

    不同业务一定要用不同 value/cacheNames

    1. 缓存与数据库不一致

    增删改必须配合 @CacheEvict@CachePut

    1. 大数据量列表缓存

    列表缓存容易占内存,建议设置更短过期时间 + 分页缓存。

    1. null 值被缓存

    unless = "#result == null" 避免。

    1. 事务与缓存顺序问题

    建议事务提交后再清缓存,否则会出现"脏缓存"。


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

注解 作用 执行方法体 典型场景
@Cacheable 查 + 缓存 缓存不存在才执行 查询接口
@CachePut 强制更新缓存 每次都执行 实时同步
@CacheEvict 删除缓存 每次都执行 增删改

九、总结

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

  • • 查询用 @Cacheable

  • • 更新删除用 @CacheEvict

  • • 实时同步用 @CachePut


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

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

相关推荐
Trouvaille ~4 分钟前
【Redis篇】Redis 主从复制:数据同步的原理与实现
数据库·redis·缓存·中间件·高可用·主从复制·后端开发
小江的记录本30 分钟前
【Spring全家桶】Spring Cloud 2023.0.x:微服务核心理论、CAP/BASE定理(附《思维导图》+《面试高频考点清单》)
java·spring boot·后端·spring·spring cloud·微服务·面试
Solis程序员33 分钟前
缓存三剑客预防策略
java·spring·缓存
我登哥MVP1 小时前
Spring Boot 从“会用”到“精通”:Model-Map原理
java·spring boot·后端·spring·servlet·maven·mybatis
程序猿乐锅1 小时前
【苍穹外卖|Day02】后台接口自测闭环:Token、DTO 与 yml 配置
java·开发语言
心之伊始1 小时前
Spring Boot Actuator + Micrometer 自定义业务指标:不只是健康检查
java·架构·源码分析·csdn
Eason_LYC1 小时前
【GetShell 实战】CVE-2026-34486 Tomcat 加密拦截器绕过:从漏洞验证到反弹 Shell 全流程
java·渗透测试·tomcat·java反序列化·rce·远程代码执行漏洞·cve-2026-34486
qq_2518364571 小时前
基于java 税务管理系统设计与实现
java·开发语言
超梦dasgg1 小时前
Java 生产环境分布式定时任务全解(实战落地版)
java·开发语言·分布式