导读:本期聚焦于雪花创作的《Spring Boot 整合 Spring Cache 实现缓存详解与实践指南》,敬请观看详情。缓存是提升应用性能最直接的手段之一,Spring Boot 对缓存操作做了高度封装,开发者只需要几个注解就能完成数据的存取和失效管理。本文围绕 Spring Boot 整合 Spring Cache 展开,先讲清楚缓存抽象层的核心原理与常用注解的用法差异,再演示基于内存和 Redis 两种缓存实现的具体接入步骤,最后结合实际开发中容易踩坑的缓存穿透、过期策略和序列化配置等问题,给出可直接落地的代码示例与配置方案,帮助你快速搭建一套稳定高效的缓存体系。

在业务量上来之后,数据库往往最先扛不住压力,此时加一层缓存几乎是标配方案。Spring Boot 提供的 Spring Cache 抽象层让缓存操作变得非常轻量,方法级别的一个注解就能把返回结果缓存起来,下次同样的请求直接从缓存读取,不必再查询数据库。本文将从原理讲到实操,完整演示如何整合默认缓存与 Redis 缓存,并覆盖常见的坑点。

Spring Boot 整合 Spring Cache 实现缓存详解与实践指南

一、Spring Cache 的核心原理与常用注解

Spring Cache 本身不提供具体的缓存实现,它只是一层抽象,背后可以对接 ConcurrentMap、Ehcache、Redis、Caffeine 等不同存储。启用缓存的入口是在配置类或启动类上标注 @EnableCaching,Spring 会扫描带有缓存注解的 Bean,为它们生成代理对象,在方法调用前后插入缓存读写逻辑。

整个过程可以简单理解为:方法被调用时,先根据缓存的 key 去缓存中查找,命中则直接返回缓存值并跳过方法体;未命中则执行方法,把返回值写入缓存,再返回给调用者。这套机制对业务代码零侵入,只需要在方法上加注解。

常用注解有五个,职责各不相同:

  • @Cacheable:先查缓存,命中直接返回,未命中执行方法并写入缓存,最常用于查询方法。
  • @CachePut:每次都执行方法,并用返回值更新缓存,适合写操作后刷新缓存。
  • @CacheEvict:删除缓存,可通过 allEntries = true 清空整个缓存空间。
  • @Caching:组合多个缓存操作,用于一个方法需要同时增删多个缓存的场景。
  • @CacheConfig:类级别的公共配置,统一指定缓存名称和 key 生成器,避免每个方法重复写。

二、快速接入默认缓存与 Redis 缓存

1. 启用缓存并使用内存缓存

第一步是引入依赖并开启缓存。如果不加任何缓存组件,Spring Boot 默认使用 ConcurrentMapCacheManager,数据存在内存里,适合单体小应用和本地调试。

@SpringBootApplication
@EnableCaching
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@Service
public class UserService {

    // value 指定缓存空间,key 指定缓存的键
    @Cacheable(value = "user", key = "#id")
    public User getUserById(Long id) {
        // 模拟一次耗时的数据库查询
        User user = userMapper.selectById(id);
        System.out.println("从数据库查询:" + id);
        return user;
    }

    // 更新数据的同时刷新缓存
    @CachePut(value = "user", key = "#user.id")
    public User updateUser(User user) {
        userMapper.updateById(user);
        return user;
    }

    // 删除数据时清除对应缓存
    @CacheEvict(value = "user", key = "#id")
    public void deleteUser(Long id) {
        userMapper.deleteById(id);
    }
}

调用 getUserById 两次,控制台只会打印一次数据库查询日志,说明第二次直接命中了缓存。这里要注意 key 的写法,#id 引用的是方法参数,如果参数是对象则用 #user.id 访问属性。

2. 切换到 Redis 作为缓存存储

内存缓存在应用重启后会丢失,多实例部署时各节点缓存也不互通,生产环境一般换成 Redis。引入依赖后,Spring Boot 会自动装配 RedisCacheManager,无需额外配置类即可生效。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 你的密码
    database: 0
    timeout: 5000

换上 Redis 之后,业务代码完全不用动,注解还是原来那些,这就是缓存抽象层的价值所在。此时用 Redis 客户端连接上去,能看到 key 的格式类似 user::1,前面是缓存空间名,后面是具体的 key。

三、生产环境必备的进阶配置

1. 自定义序列化与过期时间

默认的 Redis 序列化用的是 JDK 序列化,存进去的值是乱码二进制,可读性差且要求实体类实现序列化接口。推荐改成 JSON 格式,同时为不同缓存空间设置不同的过期时间,比如热点数据时间短一些,字典类数据时间长一些。

@Configuration
public class RedisCacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                // 设置 value 为 JSON 序列化
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()))
                // key 使用字符串序列化
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new StringRedisSerializer()))
                // 禁止缓存 null 值
                .disableCachingNullValues()
                // 默认过期时间 30 分钟
                .entryTtl(Duration.ofMinutes(30));

        // 为不同缓存空间单独设置过期时间
        Map<String, RedisCacheConfiguration> configs = new HashMap<>();
        configs.put("user", config.entryTtl(Duration.ofMinutes(10)));
        configs.put("dict", config.entryTtl(Duration.ofHours(6)));

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

JSON 序列化还有一个隐藏问题:GenericJackson2JsonRedisSerializer 会把对象的类型信息一起存进去,反序列化时能还原为原始类型。但如果缓存的是没有类型的集合或接口对象,可能会报类型转换异常,需要在实体类上做好泛型约定,或者改用带类型信息的序列化器。

2. 缓存穿透与空值处理

如果某个 id 在数据库中不存在,每次请求都不会命中缓存,请求全部打到数据库,这就是典型的缓存穿透。常见的解决办法是缓存空值:把查询结果为 null 的情况也缓存一小段时间,配合 unless 属性即可实现。

@Cacheable(value = "user", key = "#id",
        unless = "#result == null")  // 结果为 null 不缓存的写法要反过来
public User getUserById(Long id) {
    return userMapper.selectById(id);
}

// 缓存空值的写法:返回一个空对象标记,并在配置中开启 null 缓存
@Cacheable(value = "user", key = "#id", unless = "#result?.id == null")
public User getUserSafe(Long id) {
    return Optional.ofNullable(userMapper.selectById(id))
            .orElse(new User());  // 空对象代替 null
}

除了穿透问题,还要注意缓存雪崩:大量 key 在同一时间过期会导致数据库瞬时压力激增,解决思路是给过期时间加上随机偏移,比如基准 30 分钟加上 0 到 5 分钟的随机值,把过期时间点打散。

3. 使用 Caffeine 构建多级缓存

对于读多写少的场景,可以在 Redis 前面再加一层本地缓存,组成两级缓存结构。Spring Boot 对 Caffeine 的支持非常友好,引入 spring-boot-starter-cache 和 Caffeine 依赖后,在配置文件中直接声明即可。

spring:
  cache:
    type: caffeine
    caffeine:
      spec: maximumSize=1000,expireAfterWrite=5m

本地缓存命中速度极快,但要考虑多实例之间的数据一致性问题。如果对一致性要求高,本地缓存的过期时间要设置得短一些,或者在数据变更时通过消息队列广播失效通知,主动清理各节点的本地缓存。

四、常见踩坑总结

第一,@Cacheable 方法必须通过代理调用才生效,也就是必须由外部调用。同类中 this 调用自己的缓存方法,注解不会生效,这是新手最容易遇到的问题。解决办法是把缓存方法拆到单独的 Service 中,或者通过注入自身代理对象来调用。

第二,缓存的 key 拼接要保证唯一性。多个参数时如果只写一个参数作为 key,可能造成不同请求命中同一条缓存。建议使用 SpEL 表达式拼接完整 key,例如 key = "#type + ':' + #id",或者自定义 KeyGenerator 统一管理。

第三,注意事务与缓存的执行顺序。缓存写入发生在方法返回之后,如果方法在事务中执行,可能出现缓存写入成功但事务回滚的情况,导致缓存与数据库不一致。高一致性要求的场景要把缓存操作放到事务提交之后处理,可以借助 TransactionSynchronization 实现。

掌握这些内容后,在 Spring Boot 项目中落地一套缓存体系就不再是难事。默认内存缓存适合快速验证,Redis 适合绝大多数生产场景,Caffeine 加 Redis 的两级缓存则能进一步提升高并发下的读取性能,按业务规模循序渐进地升级即可。

Spring Boot缓存Spring Cache注解Redis缓存整合修改时间:2026-09-04 20:26:42

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