Spring Data Redis
RedisTemplate、序列化、配置。
Spring Data Redis:把 Redis 接进 Java
这一课你将能做到
- 说出接入 Redis 需要的依赖和 yml 配置项(Boot 3.x 的
spring.data.redis 前缀),并解释本站为什么用 StringRedisTemplate 而不是裸 RedisTemplate
- 逐行读懂
RedisConfig.java 的 4 个 Bean,说出 Lettuce 和 Jedis 的区别
- 用
opsForValue/opsForHash/opsForZSet 完成 Java 侧读写,并亲眼认清 JDK 序列化乱码坑
为什么要学
命令行操作你已经熟练,但生产代码里所有 Redis 交互都发生在 Java 里。这一步的坑最多:
- 依赖加了、配置前缀写错(
spring.redis vs spring.data.redis),启动直接失败
- 用了默认 RedisTemplate,取出来的值带一堆
\xac\xed\x00\x05 乱码,redis-cli 里根本看不懂
- 序列化方案选错,换个人接手就读不出历史数据
本站已经踩平了这些坑:RedisConfig.java(backend/src/main/java/com/codeforge/config/RedisConfig.java)就是标准答案。这一课带你逐行读懂它,之后写自己的缓存层就是在它上面加积木。
先建立图像
Java 访问 Redis 的层次结构:
你的业务代码
→ StringRedisTemplate(好用的门面:opsForValue/opsForHash...)
→ LettuceConnectionFactory(连接工厂 = 水管)
→ Lettuce(底层客户端,netty 实现,线程安全)
→ Redis 服务端
RedisTemplate<Object,Object>(默认版):值序列化用 JDK → 乱码源头
StringRedisTemplate:键值都按 UTF-8 字符串序列化 → redis-cli 可读
跟着做
步骤 1:确认依赖
backend/pom.xml 里的四件套:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- caffeine:本站本地缓存层,配合 spring-boot-starter-cache -->
spring-boot-starter-data-redis 默认带 Lettuce 客户端,无需额外引驱动。
步骤 2:yml 配置
application.yml(本机默认关):
codeforge:
redis:
enabled: false
application-prod.yml(生产开,全部走环境变量):
spring:
data:
redis:
host: ${REDIS_HOST:127.0.0.1}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
codeforge:
redis:
enabled: true
注意坐标是 spring.data.redis——Boot 3.x 从 spring.redis 迁移了过来,老教程的写法在 Boot 3.5.4 上配不上。
步骤 3:逐行读 RedisConfig.java
@Configuration
@ConditionalOnProperty(name = "codeforge.redis.enabled", havingValue = "true")
public class RedisConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory(...) { ... }
// 1. 连接工厂:"水管"。读 spring.data.redis 配置建立连接。
// Lettuce 基于 netty,连接可被所有线程共享——这是它比 Jedis 省资源的核心
@Bean
public StringRedisTemplate stringRedisTemplate(LettuceConnectionFactory factory) { ... }
// 2. 字符串读写工具:键值都是 String,人可读、跨语言,本站缓存全部走它
@Bean
public RequestRateLimiter redisRequestRateLimiter(StringRedisTemplate stringRedisTemplate) { ... }
// 3. Redis 限流器(第 3 课逐行读过的那个),注入的正是上面的模板
@Bean
public SaTokenDao saTokenDao(LettuceConnectionFactory factory) {
// 4. SaTokenDaoForRedisTemplate:把 Sa-Token 的会话存储指到 Redis
// dao.init(factory) —— 会话框架和业务共用同一条"水管"
}
}
类上的 @ConditionalOnProperty 是灵魂:enabled=false 时这 4 个 Bean 一个都不创建,登录和限流自动落回内存实现(RateLimitConfig 的 matchIfMissing = true 分支)。整组能力一键换轨。
步骤 4:亲手用 StringRedisTemplate
在测试类或临时 CommandLineRunner 里验证每种结构(对应第 4 课的命令):
@Autowired StringRedisTemplate redis;
redis.opsForValue().set("demo:k1", "hello", Duration.ofSeconds(60));
String v = redis.opsForValue().get("demo:k1"); // "hello"
redis.opsForHash().put("demo:h", "field1", "a");
redis.opsForList().rightPush("demo:q", "task1");
redis.opsForSet().add("demo:s", "a", "b");
redis.opsForZSet().incrementScore("demo:z", "lesson-1", 5);
redis.expire("demo:h", Duration.ofSeconds(120));
redis.opsForValue().increment("demo:counter"); // 原子计数
redis.delete("demo:k1");
每个 opsForXxx 对应一种结构,方法名和 redis-cli 命令几乎一一对应。
步骤 5:JDK 序列化乱码实验(看清坑在哪)
// 默认 RedisTemplate(反面教材,仅演示)
@Autowired RedisTemplate<Object, Object> rawTemplate;
rawTemplate.opsForValue().set("demo:bad", "hello");
GET demo:bad
# "\xac\xed\x00\x05t\x00\x05hello" ← JDK 序列化头
字符串被包成 Java 对象二进制:其他语言读不了、你看不懂、redis-cli 排不了错。这就是「必须用 StringRedisTemplate 或显式配 Jackson 序列化器」的原因。Sa-Token 的会话对象走 sa-token-redis-jackson(JSON 序列化),也不踩这个坑。
步骤 6:Lettuce vs Jedis 一览
| Lettuce(本站) | Jedis |
|---|
| 线程模型 | netty,连接共享,线程安全 | 一连接一实例,需连接池 |
| 资源占用 | 低 | 连接多时高 |
| Spring Boot 默认 | ✅ | 需换依赖 |
讲清楚要点
- StringRedisTemplate 是本站唯一入口:键值 UTF-8 字符串、redis-cli 可读、跨语言友好;对象一律先经 ObjectMapper 转 JSON 再存。
- @ConditionalOnProperty 让 Redis 变成可选依赖:enabled=false 时 4 个 Bean 全不创建,本机零 Redis 也能启动——降级开关是工程能力,不只是个配置项。
- Lettuce 连接工厂全局共享:Spring 管理单例,注入即可;不要每处自己 new。
- TTL 尽量在 set 时一并给(Duration 重载),而不是 set 后再 expire,避免中间崩溃留下永生 key。
- opsForXxx 返回的是视图对象,真正的网络调用发生在 .set/.get 调用时;同步 API 会阻塞调用线程,毫秒级可接受。
你可能会懵的地方
-
StringRedisTemplate 和 RedisTemplate<String,String> 一样吗?
几乎一样(前者预配好了 String 序列化器);手写泛型版还得自己 setKeySerializer/setValueSerializer,直接用现成的省心。
-
对象存 Redis 为什么用 JSON 而不是 JDK 序列化?
可读、跨语言、不怕类结构演进(JDK 序列化对 serialVersionUID 敏感,字段一改历史数据全废)。本站 sa-token-redis-jackson 和缓存层全是 JSON。
-
increment 返回 null 什么时候发生?
管道/事务中命令结果未就绪。普通同步调用非空,但判空是好习惯——本站限流器就判了。
-
Boot 2 的 spring.redis 配置为什么没生效?
Boot 3.x 改成 spring.data.redis。看教程先看 Boot 版本(本站 3.5.4 / Java 21)。
-
我要在 enabled=false 时也写缓存代码会炸吗?
直接 @Autowired StringRedisTemplate 会报 NoSuchBeanDefinitionException(Bean 根本没建)。第 12 课的 TrackCacheService 用 ObjectProvider.getIfAvailable() 拿「有则注入、无则 null」,实现空安全降级。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 直接用默认 RedisTemplate | JDK 序列化乱码、跨语言不兼容 | StringRedisTemplate + JSON |
| 配置写在 spring.redis 下 | Boot 3 不认,等于没配 | spring.data.redis |
| set 后再单独 expire | 中间崩溃 key 永生 | set(key, val, Duration) |
| 开关开着但没配 host/password | 启动或首连失败 | 核对环境变量 REDIS_HOST/PORT/PASSWORD |
| 每次 new LettuceConnectionFactory | 连接泄漏 | 注入 Spring 单例 Bean |
| 开关关时直接 @Autowired 模板 | NoSuchBeanDefinitionException | ObjectProvider.getIfAvailable() |
卡点急救
- 启动报
Unable to connect to Redis:enabled=true 但 Redis 没起或配置错。先 redis-cli PING 通,再开开关。
- 注入 StringRedisTemplate 报找不到 Bean:开关是 false,Bean 没建。打开开关,或按 ObjectProvider 方式做空安全注入。
- 值读出来带
\xac\xed:用了默认 RedisTemplate。DEL 旧数据,改用 StringRedisTemplate 重写。
- 配置写了不生效:确认文件是 application-prod.yml 且启动带了
--spring.profiles.active=prod;确认前缀是 spring.data.redis。
- 中文存取乱码:StringRedisTemplate 本身不会乱;乱的基本是默认模板,或 redis-cli 显示问题(加 --raw)。
面试常问
-
Lettuce 和 Jedis 的区别?
Lettuce 基于 netty、连接可共享、线程安全、是 Spring Boot 默认;Jedis 连接不共享需池化。本站用 Lettuce + 单一连接工厂。
-
RedisTemplate 的序列化坑你遇到过吗?
默认 JDK 序列化导致 redis-cli 乱码、跨语言不兼容、类演进敏感;用 StringRedisTemplate(字符串)或显式 Jackson 序列化器(对象)解决。
-
你们项目 Redis 怎么做到本地开发免依赖?
@ConditionalOnProperty 总开关 + 4 Bean 整组装配:关掉时会话/限流自动落回内存实现,业务代码零改动;生产 profile 默认开,连接信息走环境变量。
自测三问
- RedisConfig 的 4 个 Bean 分别是什么?各自服务什么能力?
- 为什么推荐 StringRedisTemplate?默认 RedisTemplate 的问题具体是什么?
- Boot 3 的 Redis 连接配置前缀是什么?
codeforge.redis.enabled=false 时业务代码里注入模板会发生什么?
小结一句话
Spring 侧记住三件事:StringRedisTemplate 走天下、配置挂 spring.data.redis、@ConditionalOnProperty 让整组能力可开关可降级。
延伸