CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Redis 缓存入门/课时/Spring Data Redis
RawBlame
课时目录12 节

Redis 缓存入门

  1. 01Redis 是什么
  2. 02CLI 与键命名
  3. 03String 与计数
  4. 04Hash / List / Set / ZSet
  5. 05过期与淘汰
  6. 06缓存模式
  7. 07Spring Data Redis
  8. 08会话与 Sa-Token
  9. 09Docker 运行 Redis
  10. 10Pub/Sub 入门
  11. 11测试与监控
  12. 12综合练习

Spring Data Redis

RedisTemplate、序列化、配置。

35 分钟

Spring Data Redis:把 Redis 接进 Java

这一课你将能做到

  1. 说出接入 Redis 需要的依赖和 yml 配置项(Boot 3.x 的 spring.data.redis 前缀),并解释本站为什么用 StringRedisTemplate 而不是裸 RedisTemplate
  2. 逐行读懂 RedisConfig.java 的 4 个 Bean,说出 Lettuce 和 Jedis 的区别
  3. 用 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");

然后 redis-cli 查看:

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 默认✅需换依赖

讲清楚要点

  1. StringRedisTemplate 是本站唯一入口:键值 UTF-8 字符串、redis-cli 可读、跨语言友好;对象一律先经 ObjectMapper 转 JSON 再存。
  2. @ConditionalOnProperty 让 Redis 变成可选依赖:enabled=false 时 4 个 Bean 全不创建,本机零 Redis 也能启动——降级开关是工程能力,不只是个配置项。
  3. Lettuce 连接工厂全局共享:Spring 管理单例,注入即可;不要每处自己 new。
  4. TTL 尽量在 set 时一并给(Duration 重载),而不是 set 后再 expire,避免中间崩溃留下永生 key。
  5. 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」,实现空安全降级。

错法对照

错法问题正解
直接用默认 RedisTemplateJDK 序列化乱码、跨语言不兼容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 模板NoSuchBeanDefinitionExceptionObjectProvider.getIfAvailable()

卡点急救

  1. 启动报 Unable to connect to Redis:enabled=true 但 Redis 没起或配置错。先 redis-cli PING 通,再开开关。
  2. 注入 StringRedisTemplate 报找不到 Bean:开关是 false,Bean 没建。打开开关,或按 ObjectProvider 方式做空安全注入。
  3. 值读出来带 \xac\xed:用了默认 RedisTemplate。DEL 旧数据,改用 StringRedisTemplate 重写。
  4. 配置写了不生效:确认文件是 application-prod.yml 且启动带了 --spring.profiles.active=prod;确认前缀是 spring.data.redis。
  5. 中文存取乱码:StringRedisTemplate 本身不会乱;乱的基本是默认模板,或 redis-cli 显示问题(加 --raw)。

面试常问

  • Lettuce 和 Jedis 的区别? Lettuce 基于 netty、连接可共享、线程安全、是 Spring Boot 默认;Jedis 连接不共享需池化。本站用 Lettuce + 单一连接工厂。

  • RedisTemplate 的序列化坑你遇到过吗? 默认 JDK 序列化导致 redis-cli 乱码、跨语言不兼容、类演进敏感;用 StringRedisTemplate(字符串)或显式 Jackson 序列化器(对象)解决。

  • 你们项目 Redis 怎么做到本地开发免依赖? @ConditionalOnProperty 总开关 + 4 Bean 整组装配:关掉时会话/限流自动落回内存实现,业务代码零改动;生产 profile 默认开,连接信息走环境变量。

自测三问

  1. RedisConfig 的 4 个 Bean 分别是什么?各自服务什么能力?
  2. 为什么推荐 StringRedisTemplate?默认 RedisTemplate 的问题具体是什么?
  3. Boot 3 的 Redis 连接配置前缀是什么?codeforge.redis.enabled=false 时业务代码里注入模板会发生什么?

小结一句话

Spring 侧记住三件事:StringRedisTemplate 走天下、配置挂 spring.data.redis、@ConditionalOnProperty 让整组能力可开关可降级。

延伸

  • Spring Boot Data Redis 官方文档
  • 下一课:分布式会话——Sa-Token 登录态怎么存进 Redis,多实例为什么必须有它,登录后亲眼看 satoken: key。
加载本节练习…
← 上一课缓存模式下一课 →会话与 Sa-Token