Spring Boot 使用 Redis:从入门配置到缓存实战
- 发布时间:2026-04-23 06:02:15
- 本文热度:浏览 7 赞 0 评论 0
- 文章标签: Spring Boot Redis Java
- 全文共1字,阅读约需1分钟
Redis 在 Spring Boot 里到底解决什么问题
在 Spring Boot 项目里接入 Redis,通常不是为了“把数据换个地方存”,而是为了处理三类非常具体的问题:
- 降低数据库压力:把热点数据放进缓存,减少重复查库。Spring Boot 可以直接把 Redis 作为 Spring Cache 的底层实现。(Home)
- 提升访问速度:用户资料、配置、会话、验证码、短期统计这类读多写少或允许短暂过期的数据,适合放 Redis。Redis 本身是高性能 key-value 存储,同时也常被用作缓存和消息中间层。(Home)
- 利用 Redis 数据结构做业务:除了简单 key-value,Redis 还支持 List、Set、ZSet、Hash 等结构,Spring Data Redis 对这些结构提供了直接操作封装。(Home)
如果你的业务核心是强事务、复杂关联查询、长期持久化,主存储仍然应该是 MySQL / PostgreSQL 一类关系型数据库;Redis 更适合做缓存、临时状态、分布式协调和高频读写场景的辅助存储。
Spring Boot 集成 Redis 的核心组件
Spring Boot 使用 Redis,底层通常是这套关系:
- Spring Boot 自动配置:负责根据配置文件创建连接工厂、模板类等。Spring Boot 官方文档明确提供了对 Redis 的自动配置支持。(Home)
- Spring Data Redis:提供统一的 Redis API、Template、Repository、缓存支持。(Home)
- Redis 驱动:Spring Data Redis 支持 Lettuce 和 Jedis 两种 Java 客户端。(Home)
实际开发里,Spring Boot 3.x 项目一般直接使用 spring-boot-starter-data-redis,默认场景下最常见的是 Lettuce。
先说结论:大多数项目该怎么选
1. 连接客户端选 Lettuce 还是 Jedis
Spring Data Redis 当前同时支持 Lettuce 和 Jedis。(Home)
一般建议:
- 默认选 Lettuce
- 只有在你团队明确依赖 Jedis 生态、历史项目已经统一使用 Jedis 时,再继续用 Jedis
原因不是“谁绝对更先进”,而是 Spring Boot 现在对 Lettuce 的默认集成更顺手,常规场景配置更省心。
2. 业务对象怎么序列化
不要直接使用默认 JDK 序列化把 Java 对象丢进 Redis。 RedisTemplate 默认会使用 Java 序列化;官方 API 文档也明确说明了这一点。(Home)
生产环境更常见的做法是:
- key 用
String - value 用 JSON 序列化
- 明确控制缓存前缀、过期时间、空值缓存策略
这样做的直接好处:
- Redis 里的值更容易排查
- 跨服务更友好
- 兼容性比 JDK 序列化更好
- 避免类结构变更导致反序列化问题
3. 优先用哪种接入方式
Redis 在 Spring Boot 里常见有三种用法:
StringRedisTemplate:适合纯字符串、计数器、验证码、简单键值RedisTemplate<K,V>:适合你要自己控制对象读写@Cacheable / @CacheEvict / @CachePut:适合“方法结果缓存”
结论很简单:
- 简单 key-value:用
StringRedisTemplate - 明确操作 Redis 数据结构:用
RedisTemplate - 只是想给查询结果加缓存:优先用 Spring Cache
Maven 依赖
<dependencies>
<!-- Web,仅示例需要 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 可选:如果你要自己定义 JSON 序列化,一般项目本来就会有 Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
最基本的配置
application.yml
spring:
data:
redis:
host: 127.0.0.1
port: 6379
database: 0
timeout: 3s
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 0
max-wait: 3s
server:
port: 8080
Spring Boot 3.3 的 RedisProperties 官方 API 中可以看到,配置项包含客户端类型、集群、哨兵、Jedis、Lettuce 连接池等内容。(Home)
一个能直接落地的配置类
下面这份配置做了三件事:
- 自定义
RedisTemplate<String, Object> - key 使用字符串序列化
- value / hash value 使用 JSON 序列化
- 配置统一的
RedisCacheManager
package com.example.demo.config;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import java.time.Duration;
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// key 序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// value 序列化
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY
);
GenericJackson2JsonRedisSerializer jsonRedisSerializer =
new GenericJackson2JsonRedisSerializer(objectMapper);
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisSerializationContext.SerializationPair<String> keyPair =
RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());
RedisSerializationContext.SerializationPair<Object> valuePair =
RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(keyPair)
.serializeValuesWith(valuePair)
.entryTtl(Duration.ofMinutes(30))
.disableCachingNullValues();
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
}
用 StringRedisTemplate 做最简单的读写
这个场景适合验证码、token、短信限流、计数器。
package com.example.demo.service;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.time.Duration;
@Service
public class VerifyCodeService {
private final StringRedisTemplate stringRedisTemplate;
public VerifyCodeService(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public void saveCode(String phone, String code) {
String key = "verify:code:" + phone;
stringRedisTemplate.opsForValue().set(key, code, Duration.ofMinutes(5));
}
public String getCode(String phone) {
String key = "verify:code:" + phone;
return stringRedisTemplate.opsForValue().get(key);
}
public void deleteCode(String phone) {
String key = "verify:code:" + phone;
stringRedisTemplate.delete(key);
}
}
对应接口
package com.example.demo.controller;
import com.example.demo.service.VerifyCodeService;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/code")
public class VerifyCodeController {
private final VerifyCodeService verifyCodeService;
public VerifyCodeController(VerifyCodeService verifyCodeService) {
this.verifyCodeService = verifyCodeService;
}
@PostMapping("/send")
public String send(@RequestParam String phone, @RequestParam String code) {
verifyCodeService.saveCode(phone, code);
return "ok";
}
@GetMapping("/get")
public String get(@RequestParam String phone) {
return verifyCodeService.getCode(phone);
}
@DeleteMapping("/delete")
public String delete(@RequestParam String phone) {
verifyCodeService.deleteCode(phone);
return "deleted";
}
}
用 RedisTemplate 保存对象
实体类
package com.example.demo.model;
import java.io.Serializable;
public class UserCacheDTO implements Serializable {
private Long id;
private String username;
private String email;
public UserCacheDTO() {
}
public UserCacheDTO(Long id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
}
public Long getId() {
return id;
}
public UserCacheDTO setId(Long id) {
this.id = id;
return this;
}
public String getUsername() {
return username;
}
public UserCacheDTO setUsername(String username) {
this.username = username;
return this;
}
public String getEmail() {
return email;
}
public UserCacheDTO setEmail(String email) {
this.email = email;
return this;
}
}
Service
package com.example.demo.service;
import com.example.demo.model.UserCacheDTO;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.time.Duration;
@Service
public class UserCacheService {
private final RedisTemplate<String, Object> redisTemplate;
public UserCacheService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void save(UserCacheDTO user) {
String key = "user:profile:" + user.getId();
redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(30));
}
public UserCacheDTO get(Long userId) {
String key = "user:profile:" + userId;
Object value = redisTemplate.opsForValue().get(key);
return value == null ? null : (UserCacheDTO) value;
}
public void delete(Long userId) {
String key = "user:profile:" + userId;
redisTemplate.delete(key);
}
}
这种写法的本质很直接: 你自己决定 key 命名、过期时间、读写逻辑和数据结构,适合业务有明显 Redis 设计诉求的场景。
更推荐的缓存写法:Spring Cache
如果你的目标只是“给查询结果加缓存”,不要一上来就自己写 redisTemplate.opsForValue()。 Spring Data Redis 提供了对 Spring Cache 抽象的实现,可以通过 RedisCacheManager 作为底层缓存管理器。(Home)
开启缓存后,业务代码可以非常直接
package com.example.demo.service;
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 UserService {
@Cacheable(cacheNames = "user", key = "#userId")
public String getUserById(Long userId) {
System.out.println("query db...");
return "user-" + userId;
}
@CachePut(cacheNames = "user", key = "#userId")
public String updateUser(Long userId, String username) {
System.out.println("update db...");
return username;
}
@CacheEvict(cacheNames = "user", key = "#userId")
public void deleteUser(Long userId) {
System.out.println("delete db...");
}
}
三个注解的职责要分清
@Cacheable:先查缓存,没有才执行方法,并把结果放入缓存@CachePut:一定执行方法,并把返回值更新到缓存@CacheEvict:删除缓存
这是 Redis 在 Spring Boot 里最常见、也是最适合业务开发团队协作的接入方式。
实战里最容易踩的坑
1. 过期时间不设,缓存迟早失控
Redis 不是无限空间。 如果你把对象长期塞进去却不设置 TTL,结果通常只有两个:
- 内存不断上涨
- 线上排查时根本分不清哪些 key 还有效
结论很明确:绝大多数业务缓存都应该设置过期时间。
2. key 命名混乱,后期运维很痛苦
推荐统一使用带业务前缀的命名方式,例如:
user:profile:1001
order:detail:20260001
verify:code:13800000000
不要写成:
1001
user1001
cache_key_1
好的 key 设计至少要满足:
- 一眼看出业务含义
- 支持按前缀筛查
- 支持分环境、分模块隔离
3. 默认序列化不适合长期维护
RedisTemplate 默认使用 JDK 序列化。官方 API 文档已经明确说明默认行为,并建议字符串密集场景使用 StringRedisTemplate。(Home)
默认 JDK 序列化的问题不是“不能用”,而是:
- Redis 可读性差
- 类变更后兼容性差
- 不利于跨服务共享
- 排查问题时不直观
所以生产环境里通常会主动替换为 JSON 序列化。
4. 把 Redis 当数据库硬用
Redis 很适合缓存、计数、排行榜、会话、分布式锁等场景,但它不是关系型数据库替代品。 尤其是下面这些需求,不应该强行往 Redis 上套:
- 多表关联
- 复杂筛选和分页
- 强事务一致性
- 长期核心业务数据存储
5. 缓存更新策略没设计好,会读到脏数据
只接入 Redis 很简单,真正麻烦的是缓存和数据库的一致性策略。 最常见的两种方式:
Cache Aside(旁路缓存)
流程是:
- 读:先查缓存,没命中再查数据库,并回填缓存
- 写:先更新数据库,再删除缓存
这是最常用、最容易理解的一种策略。
Write Through / Write Behind
这类策略由缓存层参与写入路径,复杂度更高,普通 Spring Boot 业务系统里并不常作为第一选择。
大部分中后台项目,优先把 Cache Aside 用稳。
Redis 常见业务场景
1. 验证码
特点:
- 生命周期短
- 高并发读取
- 必须带 TTL
适合用 StringRedisTemplate。
2. 用户信息缓存
特点:
- 热点读
- 数据有明确主键
- 可容忍短时间缓存延迟
适合用 @Cacheable 或 RedisTemplate。
3. 购物车
特点:
- 频繁更新
- 可按用户隔离
- 常需要 Hash 结构存储多个字段
适合用 RedisTemplate.opsForHash()。
4. 排行榜
特点:
- 需要排序
- 需要按分值范围查询
适合用 Redis 的 ZSet。
5. 分布式计数与限流
特点:
- 原子递增
- 通常要配合过期时间窗口
适合用 INCR 相关能力,通过 Spring Data Redis 也可以完成。
一个稍微完整一点的用户缓存示例
下面给一个典型模式:数据库查用户信息,Redis 只做缓存层。
模拟 DAO
package com.example.demo.repository;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public String findUsernameById(Long userId) {
// 这里仅做演示,真实项目里应查询 MySQL
return "db-user-" + userId;
}
}
Service
package com.example.demo.service;
import com.example.demo.repository.UserRepository;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class UserQueryService {
private final UserRepository userRepository;
public UserQueryService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Cacheable(cacheNames = "userName", key = "#userId")
public String getUsername(Long userId) {
return userRepository.findUsernameById(userId);
}
}
Controller
package com.example.demo.controller;
import com.example.demo.service.UserQueryService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserQueryController {
private final UserQueryService userQueryService;
public UserQueryController(UserQueryService userQueryService) {
this.userQueryService = userQueryService;
}
@GetMapping("/user/name")
public String getUsername(@RequestParam Long userId) {
return userQueryService.getUsername(userId);
}
}
第一次访问会执行数据库查询,第二次开始会直接命中 Redis。 这类模式就是 Spring Boot 使用 Redis 最典型的落地方式。
版本区别要说清楚
Spring Boot 2.x 与 Spring Boot 3.x 的使用差异
1. JDK 基线不同
Spring Data Redis 3.1 文档明确要求 JDK 17+ 和 Spring Framework 6.x。(Home) 这意味着在 Spring Boot 3.x 体系中,你至少应以 JDK 17 为基础来使用当前主流版本。
2. 配置前缀要注意
在 Spring Boot 3.x 里,Redis 常见配置路径是:
spring:
data:
redis:
如果你项目还停留在部分旧版写法或旧博客示例,看到的是别的配置层级,就要按你实际 Spring Boot 版本核对,不要直接照抄。
3. 依赖兼容不要手工乱配
Spring Data 官方文档明确建议通过 Spring Data Release Train BOM 管理兼容版本。对于 Spring Boot 项目,更简单的做法是:直接跟随 Spring Boot 自带依赖管理,不要单独硬改 Spring Data Redis 版本。(Home)
4. 老博客里的序列化代码不一定还能直接照搬
很多旧文章使用的序列化配置、ObjectMapper 写法、甚至缓存配置 API,放到 Spring Boot 3.x / Spring Framework 6.x 环境里可能需要调整。 因此看示例时一定先确认文章对应版本。
连接模式不止单机
Spring Data Redis 官方文档明确给出了多种连接模式,包括:
- Standalone(单机)
- Master/Replica
- Sentinel
- Cluster
不同模式对应不同配置。(Home)
中小项目开发环境通常先用单机; 生产环境是否用 Sentinel 或 Cluster,要看你的高可用和容量要求,而不是“Redis 上线就必须集群”。
一套更靠谱的落地建议
如果你在做一个普通 Spring Boot 后端项目,比较稳妥的实践是:
- 先把 Redis 用在缓存,不要一开始就塞太多复杂业务状态
- 查询缓存优先用 Spring Cache
- 明确设置 TTL
- key 命名带业务前缀
- value 使用 JSON 序列化
- 热点接口要考虑缓存击穿、穿透、雪崩
- 更新数据库时配合删除缓存,避免脏数据
- 不要把 Redis 当关系型数据库替代品
小结
Spring Boot 使用 Redis,重点从来不在“怎么连上 Redis”,而在三件事:
- 为什么要用 Redis
- 用 Redis 解决哪类问题
- 怎样把序列化、TTL、key 设计、缓存一致性这些基础问题一次做对
真正的实战经验差异,通常也不体现在 set/get 代码上,而体现在:
- 你有没有明确缓存边界
- 你有没有设计好失效策略
- 你有没有控制住数据结构和 key 命名
- 你有没有避免把 Redis 用成一个难以维护的“第二数据库”
把这些问题处理好,Spring Boot + Redis 才会真正成为项目性能优化工具,而不是后续故障源头。