导读:本期聚焦于下班再修创作的《Spring Boot 如何整合使用 @Cacheable 注解实现缓存?完整配置与实战详解》,敬请观看详情。接口响应慢、数据库压力大,往往是因为频繁执行重复查询。Spring 从 3.1 开始提供了声明式缓存抽象,只需在方法上加一个 code@Cacheable/code 注解,就能让相同的请求直接命中缓存,省去重复计算和数据库访问。本文围绕 Spring Boot 整合缓存的核心注解展开,先讲清 @Cacheable、@CachePut、@CacheEvict 各自的适用场景与执行时机,再演示基于 ConcurrentHashMap 的默认缓存与接入 Redis 的完整配置步骤,包括依赖引入、CacheManager 配置、过期时间设置以及基于 JSON 的序列化方案,最后汇总自调用失效、key 设计不合理、序列化报错等常见踩坑点,帮助你快速在生产项目中落地一套可靠的缓存方案。

缓存是提升系统性能最直接的手段之一。Spring Boot 对缓存的支持做得相当轻量,不需要手写 RedisTemplate 的增删查逻辑,只要在方法上声明 @Cacheable,框架就会自动拦截方法调用:先查缓存,命中则直接返回,不命中才真正执行方法体并把结果写入缓存。本文从一个可运行的例子出发,完整讲解 Spring Boot 整合缓存的配置方法、各核心注解的区别,以及接入 Redis 后的进阶用法和常见坑。

Spring Boot 如何整合使用 @Cacheable 注解实现缓存?完整配置与实战详解

一、开启缓存支持与 @Cacheable 基础用法

Spring Boot 整合缓存的第一步是引入 spring-boot-starter-cache 依赖,并在配置类上添加 @EnableCaching 注解。这个注解会激活缓存代理,让容器中带有缓存注解的 Bean 被动态代理包裹。Maven 依赖如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

接着在启动类或任意配置类上加 @EnableCaching

@SpringBootApplication
@EnableCaching  // 开启缓存代理
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

使用时只需标注在查询方法上。value 属性指定缓存名称(可以理解为命名空间),key 属性指定缓存键,支持 SpEL 表达式:

@Service
public class UserService {

    @Cacheable(value = "userCache", key = "#id")
    public User getUserById(Long id) {
        System.out.println("真正执行查询方法, id=" + id);
        return userMapper.selectById(id);
    }
}

第一次调用 getUserById(1L) 时控制台会打印日志并查库,第二次调用相同的 id 则直接返回缓存结果,方法体不会执行,这就是 @Cacheable 最核心的行为:先查缓存,命中就跳过方法执行。如果不指定 key,Spring 会使用 SimpleKeyGenerator,把所有入参拼成一个组合键,只有一个参数时直接用该参数作为键。

需要注意 key 的唯一性问题。如果同一个缓存名称下有多个方法,或者方法的参数本身不能唯一标识一条数据,就必须显式指定 key。SpEL 中常用的写法包括 #id(取参数)、#user.id(取对象属性)、#result.id(取返回值,仅 @CachePut 可用)以及 T(java.util.Objects).hash(#a, #b) 这类静态方法调用。

二、@CachePut、@CacheEvict 与 @Caching 的配合使用

缓存不能只进不出,否则更新数据后读到的永远是旧值。Spring 提供了一组注解各司其职,理解它们的执行时机是关键。

@Cacheable 是双行为注解:先查后写。@CachePut 则不同,它每次都会执行方法体,只是把返回值更新到缓存中,适用于更新操作后刷新缓存。@CacheEvict 用于删除缓存,可以按 key 删除,也可以用 allEntries = true 清空整个缓存名称下的所有条目。三者对比如下:

注解是否执行方法对缓存的操作典型场景
@Cacheable命中则不执行查询,未命中则写入查询方法
@CachePut总是执行用返回值覆盖缓存更新方法
@CacheEvict总是执行删除指定 key 或全部条目删除、批量更新

一个典型的增删改查组合写法如下:

@Service
public class UserService {

    @Cacheable(value = "userCache", key = "#id")
    public User getUserById(Long id) { ... }

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

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

    // 清空整个 userCache,常用于批量操作后
    @CacheEvict(value = "userCache", allEntries = true)
    public void reloadAll() { ... }
}

当一个方法需要同时应用多种缓存操作时,可以使用 @Caching 组合注解,例如同时清空两个缓存空间:

@Caching(evict = {
    @CacheEvict("userCache"),
    @CacheEvict(value = "deptCache", allEntries = true)
})
public void importUsers(List<User> users) { ... }

三、切换到 Redis 缓存并配置序列化与过期时间

不加任何额外依赖时,Spring Boot 使用 ConcurrentMapCacheManager,数据存在 JVM 内存的 ConcurrentHashMap 中。这种方式无法设置过期时间,不支持分布式共享,应用重启缓存全部丢失,只适合开发调试或数据量极小的场景。生产环境基本都要换成 Redis。

引入 Redis 依赖后,Spring Boot 会自动配置 RedisCacheManager

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

在 application.yml 中配置连接信息:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: yourpassword
    database: 0

默认的序列化器是 JDK 序列化,存进 Redis 的值会变成一串不可读的字节码,而且要求实体类实现 Serializable 接口,反序列化时还容易出现版本不一致的问题。推荐自定义配置为 JSON 序列化,同时为不同缓存设置不同的过期时间:

@Configuration
public class CacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30))  // 默认过期 30 分钟
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .disableCachingNullValues();

        Map<String, RedisCacheConfiguration> configs = new HashMap<>();
        // 单独为某个缓存空间设置更长的过期时间
        configs.put("userCache", config.entryTtl(Duration.ofHours(2)));

        return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .withInitialCacheConfigurations(configs)
                .build();
    }
}

配置完成后,业务代码不需要任何改动,@Cacheable 注解照常使用,底层实现已经悄悄从本地 Map 换成了 Redis。这种面向注解的声明式风格最大的好处就是业务与缓存实现解耦,将来想换成 Caffeine 或多级缓存,只调整 CacheManager 即可。

四、常见踩坑点与排查思路

坑一:同类内部调用导致缓存失效。缓存是基于 AOP 代理实现的,如果在同一个类中,方法 A 直接调用带 @Cacheable 的方法 B,走的是 this 引用而不是代理对象,注解完全不生效。解决办法是把方法拆到另一个 Bean 中,或者注入自身代理、使用 AopContext.currentProxy()。

坑二:缓存 null 值。查询数据库没有结果时返回 null,默认情况下 Spring 不会缓存 null,导致不存在的数据每次都打到数据库,容易引发缓存穿透。可以在方法上设置 unless = "#result == null" 控制写入条件,或者反过来对 null 做短暂缓存来挡住穿透流量。

坑三:key 冲突。不同方法使用了相同的缓存名和 key 规则,导致数据互相覆盖。建议在 key 中带上方法语义前缀,例如 key = "'detail:' + #id",保证不同方法的数据隔离。

坑四:private 方法加注解无效。同样受代理机制限制,@Cacheable 只能标注在 public 方法上,private、final、static 方法都无法被代理拦截,注解会被静默忽略,不报错但也不生效,排查时容易被忽略。

掌握这几个要点后,Spring Boot 的缓存整合基本就没有盲区了。核心思路是:注解声明缓存语义,CacheManager 决定存储实现,代理机制负责拦截执行。理解了这三层关系,无论是本地缓存、Redis 还是更复杂的多级缓存方案,都可以平滑迁移。

Spring BootCacheable缓存修改时间:2026-09-03 04:44:49

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260903/49336.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。