Spring Boot 如何整合 Spring Data Redis 实现缓存功能?

来源:站长查询作者:毕达哥头衔:网络博主
导读:本期聚焦于毕达哥创作的《Spring Boot 如何整合 Spring Data Redis 实现缓存功能?》,敬请观看详情。为什么直接查询数据库时接口响应很慢,加入Redis缓存后性能就能有明显提升?核心原因在于Redis基于内存存储的特性,比磁盘IO的数据库查询速度快上几个数量级。在Spring Boot项目中整合Spring Data Redis实现缓存,能够把高频访问的热点数据暂时存放在Redis中,后续请求优先从缓存获取数据,减少数据库查询压力。本文会详细讲解整合过程的依赖配置、序列化方式选择、缓存注解使用以及实际业务场景下的缓存更新策略,还会对常见的缓存穿透、缓存击穿问题给出对应的解决方案,帮助开发者快速掌握这套缓存实现的完整流程。

在现代Web应用开发中,缓存是提升系统性能、降低数据库压力的核心手段之一。Redis作为高性能的键值型内存数据库,支持多种数据结构,读写速度极快,和Spring Boot搭配使用时,通过Spring Data Redis模块可以快速实现缓存功能,不需要手动编写大量的Redis操作代码。很多业务场景下,比如商品详情查询、用户信息获取、配置项读取等,数据变更频率低但访问频率高,这类接口非常适合接入缓存。

Spring Boot 如何整合 Spring Data Redis 实现缓存功能?

整合前的环境准备与依赖引入

首先要确保本地或者服务器上已经安装并启动了Redis服务,默认情况下Redis会监听6379端口,如果没有修改配置的话,后续连接时直接使用默认端口即可。如果是本地开发,也可以通过Docker快速启动一个Redis实例,执行docker run -d -p 6379:6379 redis命令就能拉取官方镜像并启动容器,这种方式不需要手动安装Redis环境,适合快速验证功能。

接下来在Spring Boot项目的pom.xml文件中添加对应的依赖,核心依赖是Spring Data Redis的 starter,它会自动引入Redis客户端、连接池等必要的组件,不需要我们手动管理版本。如果项目用的是Gradle构建工具,对应的依赖配置方式也类似,只需要在build.gradle的dependencies块中添加对应的依赖声明即可。添加依赖后,Maven或者Gradle会自动下载相关jar包,项目构建完成后就可以使用Spring Data Redis提供的各种功能了。

这里需要注意,Spring Boot 2.x版本之后,Spring Data Redis默认使用的客户端从Jedis换成了Lettuce,Lettuce基于Netty实现,支持异步操作和更好的连接池管理,性能上比Jedis更有优势。如果之前的项目用的是Jedis,想要切换回来也可以手动排除Lettuce依赖,再引入Jedis的依赖,不过大多数场景下使用默认的Lettuce就足够了。添加依赖的示例代码如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池依赖,Lettuce需要commons-pool2支持连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

Redis核心配置与序列化方案选择

依赖引入完成后,需要在application.yml或者application.properties配置文件中添加Redis的连接信息,包括Redis服务的地址、端口、密码、数据库索引等。如果Redis没有设置密码,密码项可以留空或者不配置,数据库索引默认是0,Redis默认有16个数据库,索引从0到15,可以根据业务需要选择不同的库来存储不同的缓存数据,避免不同业务的缓存键名冲突。

除了基础连接配置,还需要配置Redis的连接池参数,比如最大连接数、最小空闲连接数、连接超时时间等,合理的连接池配置可以避免频繁创建销毁连接带来的性能损耗,也能防止连接数过多打满Redis服务。比如最大连接数可以根据业务的并发量来设置,一般中小项目设置为8到16就足够,最小空闲连接数可以设置为2到4,保证有一定数量的连接随时可用,减少获取连接的等待时间。

配置中最重要的一点是Redis的序列化方式,Spring Data Redis默认使用的序列化器是JdkSerializationRedisSerializer,它会把对象序列化成字节数组存储到Redis中,这种方式存储的内容是二进制的,直接通过Redis客户端查看的时候可读性很差,而且要求存储的对象实现Serializable接口,有一定的局限性。实际开发中更推荐使用Jackson2JsonRedisSerializer,它可以把对象序列化成JSON格式的字符串,存储在Redis中的内容可读性强,也不需要对象实现Serializable接口,不过需要注意,反序列化的时候如果是泛型对象,可能需要额外配置类型信息,避免反序列化失败。配置序列化器的代码示例如下:

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // 配置Jackson序列化器
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 开启类型信息,解决反序列化泛型对象的问题
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSerializer.setObjectMapper(om);

        // 键使用String序列化器
        template.setKeySerializer(new StringRedisSerializer());
        // 值使用Jackson序列化器
        template.setValueSerializer(jacksonSerializer);
        // Hash结构的键和值也分别使用String和Jackson序列化器
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jacksonSerializer);

        template.afterPropertiesSet();
        return template;
    }
}

对应的application.yml配置示例:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 
    database: 0
    lettuce:
      pool:
        max-active: 8
        max-idle: 4
        min-idle: 2
        max-wait: 1000ms
      shutdown-timeout: 100ms

缓存注解的使用与业务场景实践

Spring Data Redis整合完成后,最常用的方式是使用Spring的缓存注解来实现缓存功能,不需要手动调用RedisTemplate的API来操作缓存,大大简化了开发流程。核心的缓存注解包括@Cacheable@CacheEvict@CachePut,每个注解有不同的使用场景。

@Cacheable注解用于查询方法,作用是先检查缓存中是否有对应的数据,如果有就直接返回缓存数据,不执行方法体;如果没有,就执行方法体查询数据,把结果存入缓存后再返回。这个注解的常用属性有value(缓存名称,相当于缓存的分组)、key(缓存的键,支持SpEL表达式)、unless(满足条件就不缓存,比如结果为null的时候不缓存)。比如在商品详情查询接口上使用这个注解,就可以把商品信息缓存起来,后续相同商品ID的请求就不需要再查数据库了。

@CacheEvict注解用于删除或者更新方法,作用是当数据发生变更的时候,清除对应的缓存,避免缓存数据和数据库数据不一致。它的属性除了value和key之外,还有allEntries,如果设置为true的话,会清除整个缓存名称下的所有缓存,比如商品信息批量更新的时候,可以清除所有商品缓存;还有beforeInvocation,设置为true的话会在方法执行前清除缓存,默认是方法执行后清除。

@CachePut注解用于更新方法,作用是方法执行后把结果存入缓存,不管缓存中是否已经有数据,都会执行方法体,然后把结果更新到缓存中,适合需要强制更新缓存的场景。下面是实际的业务代码示例,包含查询、更新、删除三个场景的缓存使用:

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class ProductService {

    // 模拟数据库存储
    private Map<Long, Product> productDb = new HashMap<>();

    // 查询商品,使用@Cacheable,key是商品ID,结果为null的时候不缓存
    @Cacheable(value = "product", key = "#productId", unless = "#result == null")
    public Product getProductById(Long productId) {
        System.out.println("查询数据库,商品ID:" + productId);
        return productDb.get(productId);
    }

    // 更新商品,使用@CachePut,更新后把新商品信息存入缓存
    @CachePut(value = "product", key = "#product.id")
    public Product updateProduct(Product product) {
        System.out.println("更新数据库,商品ID:" + product.getId());
        productDb.put(product.getId(), product);
        return product;
    }

    // 删除商品,使用@CacheEvict,删除后清除对应缓存
    @CacheEvict(value = "product", key = "#productId")
    public void deleteProduct(Long productId) {
        System.out.println("删除数据库数据,商品ID:" + productId);
        productDb.remove(productId);
    }

    // 清空所有商品缓存
    @CacheEvict(value = "product", allEntries = true)
    public void clearAllProductCache() {
        System.out.println("清空所有商品缓存");
    }
}

// 商品实体类
class Product {
    private Long id;
    private String name;
    private BigDecimal price;

    // 省略getter、setter、构造方法
}

使用缓存注解之前,需要在Spring Boot的启动类上添加@EnableCaching注解,开启Spring的缓存功能,否则这些缓存注解不会生效。另外,缓存的键生成规则可以自定义,如果不指定key属性的话,Spring会使用默认的键生成器,默认会把方法的参数作为键,不过实际开发中建议显式指定key,避免不同方法的参数相同导致缓存键冲突。

缓存常见问题与解决方案

在实际使用缓存的过程中,会遇到一些常见的问题,比如缓存穿透、缓存击穿、缓存雪崩,这些问题如果不处理,可能会导致数据库压力骤增,甚至服务不可用。缓存穿透指的是查询一个不存在的数据,缓存中没有,数据库中也没有,每次请求都会打到数据库上,如果有恶意请求大量查询不存在的数据,就会把数据库打垮。解决缓存穿透的常用方案有两种,一种是缓存空值,就是当查询结果为null的时候,也把null存入缓存,设置较短的过期时间,这样下次相同的请求就会直接返回null,不会查数据库;另一种是使用布隆过滤器,把所有存在的商品ID都放入布隆过滤器,请求过来的时候先检查布隆过滤器,如果不存在就直接返回,不会查缓存和数据库。

缓存击穿指的是一个热点key突然过期,此时有大量请求同时过来查询这个key,这些请求都会发现缓存中没有数据,然后同时去查询数据库,导致数据库压力瞬间增大。解决缓存击穿的常用方案是设置热点key永不过期,或者加分布式锁,当缓存失效的时候,只有一个请求能去查询数据库,其他请求等待,查询完成后把数据存入缓存,后续请求直接从缓存获取。使用分布式锁的话,可以用Redis的setnx命令实现,只有设置成功的请求才能去查数据库,其他请求自旋等待缓存更新。

缓存雪崩指的是大量缓存key在同一时间过期,或者Redis服务宕机,导致所有请求都打到数据库上,造成数据库崩溃。解决缓存雪崩的方案包括给缓存key的过期时间加上随机值,避免大量key同时过期;使用Redis集群,避免单点故障;也可以做服务降级,当缓存不可用的时候,直接返回默认数据或者错误提示,减少数据库的压力。另外,也可以开启Redis的持久化功能,即使Redis重启,也可以从持久化文件中恢复数据,减少缓存失效的影响。

除了这些问题,还需要注意缓存和数据库的数据一致性问题,一般来说,先更新数据库,再删除缓存是比较推荐的处理方式,避免更新数据库后更新缓存失败导致缓存数据旧的问题。如果先删除缓存再更新数据库,可能会出现删除缓存后,还没更新数据库,就有请求过来查询数据,把旧数据又存入缓存的情况。不过这种方案也不是完全没问题,比如更新数据库成功后删除缓存失败,还是会出现不一致,这时候可以引入消息队列重试删除,或者设置缓存的过期时间作为兜底方案。

Spring BootSpring Data Redis缓存修改时间:2026-08-30 04:15:58

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