当前位置: 首页 > news >正文

【Redis八股|第10篇】Spring Boot 项目整合 Redis 实战

前言

前面 Redis 系列已经讲了很多理论知识,包括:

  • Redis 是什么
  • Redis 为什么快
  • 五大数据类型
  • String 常见命令
  • 缓存穿透、击穿、雪崩
  • 过期删除和内存淘汰
  • RDB 和 AOF
  • 分布式锁
  • 主从复制、哨兵、集群

这一篇我们回到 Java 项目中,写一个更贴近实际开发的内容:Spring Boot 整合 Redis

在 Spring Boot 项目中,Redis 常见用途包括:

  • 保存验证码
  • 缓存用户信息
  • 缓存商品详情
  • 实现阅读量自增
  • 实现简单限流
  • 保存登录 token

这一篇我们就以 Spring Boot 项目为例,演示如何引入 Redis、配置 Redis、使用StringRedisTemplate操作 Redis,并实现几个常见功能。

一、引入 Redis 依赖

在 Spring Boot 项目中,先添加 Redis 依赖。

<!-- pom.xml --><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>

大多数 Spring Boot Web 项目本身已经包含 Jackson。

二、配置 Redis 连接

application.yml中配置 Redis。

spring:redis:host:localhostport:6379password:database:0

说明:

配置含义
hostRedis 地址
portRedis 端口
passwordRedis 密码,没有可不填
database使用第几个库

如果是 Spring Boot 3 或较新版本,配置前缀可能是:

spring:data:redis:host:localhostport:6379database:0

具体以项目 Spring Boot 版本为准。

三、StringRedisTemplate 和 RedisTemplate 的区别

Spring Boot 中常用两个模板:

RedisTemplateStringRedisTemplate

1. StringRedisTemplate

StringRedisTemplate的 key 和 value 默认都是字符串。

适合操作:

  • 字符串
  • JSON 字符串
  • token
  • 验证码
  • 计数器

比如:

stringRedisTemplate.opsForValue().set("name","zhangsan");

2. RedisTemplate

RedisTemplate更通用,可以操作对象。

但默认序列化方式如果没配置好,可能会出现乱码或不易读的问题。

学习和项目初期,更推荐先使用:

StringRedisTemplate

对象可以手动转成 JSON 字符串保存。

这种方式可读性更好,也更容易排查问题。

四、创建统一 Result 返回类

为了和前面的 Spring Boot 系列风格一致,这里继续使用统一返回结果。

publicclassResult{privateIntegercode;privateStringmsg;privateObjectdata;publicResult(){}publicResult(Integercode,Stringmsg,Objectdata){this.code=code;this.msg=msg;this.data=data;}publicstaticResultsuccess(){returnnewResult(1,"success",null);}publicstaticResultsuccess(Objectdata){returnnewResult(1,"success",data);}publicstaticResulterror(Stringmsg){returnnewResult(0,msg,null);}// getter、setter 省略}

实际项目中也可以使用泛型版Result<T>

五、案例一:保存验证码

1. 功能需求

用户输入手机号,请求发送验证码。

后端生成 6 位验证码,保存到 Redis,并设置 5 分钟过期。

key 设计:

login:code:手机号

比如:

login:code:13800000000

value 保存验证码:

952713

2. Controller 层

@RestController@RequestMapping("/code")publicclassCodeController{@AutowiredprivateCodeServicecodeService;@PostMapping("/send")publicResultsendCode(@RequestParamStringphone){codeService.sendCode(phone);returnResult.success();}@PostMapping("/check")publicResultcheckCode(@RequestParamStringphone,@RequestParamStringcode){booleanresult=codeService.checkCode(phone,code);if(result){returnResult.success("验证码正确");}returnResult.error("验证码错误或已过期");}}

3. Service 层

publicinterfaceCodeService{voidsendCode(Stringphone);booleancheckCode(Stringphone,Stringcode);}

实现类:

@ServicepublicclassCodeServiceImplimplementsCodeService{@AutowiredprivateStringRedisTemplatestringRedisTemplate;@OverridepublicvoidsendCode(Stringphone){Stringcode=String.valueOf(newRandom().nextInt(900000)+100000);Stringkey="login:code:"+phone;stringRedisTemplate.opsForValue().set(key,code,5,TimeUnit.MINUTES);System.out.println("验证码:"+code);}@OverridepublicbooleancheckCode(Stringphone,Stringcode){Stringkey="login:code:"+phone;StringredisCode=stringRedisTemplate.opsForValue().get(key);returncode!=null&&code.equals(redisCode);}}

4. 文字说明

这里使用:

stringRedisTemplate.opsForValue().set(key,code,5,TimeUnit.MINUTES);

表示把验证码保存到 Redis,并设置 5 分钟过期。

校验时:

stringRedisTemplate.opsForValue().get(key);

如果 Redis 中查不到,说明验证码不存在或已经过期。

这个案例体现了 Redis 非常适合保存临时数据。

六、案例二:缓存商品详情

1. 功能需求

查询商品详情时,先查 Redis。

如果 Redis 有缓存,直接返回。

如果 Redis 没有缓存,再查 MySQL,并把查询结果写入 Redis。

key 设计:

product:商品id

比如:

product:1001

2. 商品实体类

publicclassProduct{privateLongid;privateStringname;privateBigDecimalprice;privateIntegerstock;// getter、setter 省略}

3. Mapper 层

@MapperpublicinterfaceProductMapper{@Select("select id, name, price, stock from product where id = #{id}")ProductgetById(Longid);}

4. Service 层

publicinterfaceProductService{ProductgetById(Longid);}

实现类:

@ServicepublicclassProductServiceImplimplementsProductService{@AutowiredprivateProductMapperproductMapper;@AutowiredprivateStringRedisTemplatestringRedisTemplate;@AutowiredprivateObjectMapperobjectMapper;@OverridepublicProductgetById(Longid){Stringkey="product:"+id;Stringjson=stringRedisTemplate.opsForValue().get(key);if(json!=null&&!json.isEmpty()){try{returnobjectMapper.readValue(json,Product.class);}catch(JsonProcessingExceptione){thrownewRuntimeException("商品缓存解析失败",e);}}Productproduct=productMapper.getById(id);if(product==null){stringRedisTemplate.opsForValue().set(key,"",2,TimeUnit.MINUTES);returnnull;}try{StringproductJson=objectMapper.writeValueAsString(product);stringRedisTemplate.opsForValue().set(key,productJson,30,TimeUnit.MINUTES);returnproduct;}catch(JsonProcessingExceptione){thrownewRuntimeException("商品缓存序列化失败",e);}}}

5. Controller 层

@RestController@RequestMapping("/products")publicclassProductController{@AutowiredprivateProductServiceproductService;@GetMapping("/{id}")publicResultgetById(@PathVariableLongid){Productproduct=productService.getById(id);if(product==null){returnResult.error("商品不存在");}returnResult.success(product);}}

6. 文字说明

这个案例就是典型的缓存查询流程:

先查 Redis Redis 命中:直接返回 Redis 未命中:查 MySQL MySQL 有数据:写入 Redis MySQL 无数据:缓存空值

这里还加入了空值缓存:

stringRedisTemplate.opsForValue().set(key,"",2,TimeUnit.MINUTES);

这样可以缓解缓存穿透。

如果查询一个不存在的商品 id,短时间内不会反复打到数据库。

七、案例三:阅读量自增

1. 功能需求

每次用户访问文章详情时,文章阅读量加 1。

key 设计:

article:文章id:readCount

比如:

article:1001:readCount

2. Service 代码

@ServicepublicclassArticleService{@AutowiredprivateStringRedisTemplatestringRedisTemplate;publicLongincrReadCount(LongarticleId){Stringkey="article:"+articleId+":readCount";returnstringRedisTemplate.opsForValue().increment(key);}publicStringgetReadCount(LongarticleId){Stringkey="article:"+articleId+":readCount";returnstringRedisTemplate.opsForValue().get(key);}}

3. Controller 代码

@RestController@RequestMapping("/articles")publicclassArticleController{@AutowiredprivateArticleServicearticleService;@PostMapping("/{id}/read")publicResultread(@PathVariableLongid){Longcount=articleService.incrReadCount(id);returnResult.success(count);}@GetMapping("/{id}/readCount")publicResultgetReadCount(@PathVariableLongid){returnResult.success(articleService.getReadCount(id));}}

4. 文字说明

这里使用的是 Redis String 的自增能力:

increment(key)

它对应 Redis 中的:

incr key

适合做:

  • 阅读量
  • 点赞数
  • 接口访问次数
  • 短信发送次数

需要注意,如果阅读量最终要持久化到 MySQL,可以定时把 Redis 中的数据同步回数据库。

八、案例四:简单接口限流

1. 功能需求

限制用户 60 秒内最多发送 5 次验证码。

key 设计:

limit:sendCode:手机号

比如:

limit:sendCode:13800000000

2. Service 代码

publicvoidcheckSendCodeLimit(Stringphone){Stringkey="limit:sendCode:"+phone;Longcount=stringRedisTemplate.opsForValue().increment(key);if(count!=null&&count==1){stringRedisTemplate.expire(key,60,TimeUnit.SECONDS);}if(count!=null&&count>5){thrownewRuntimeException("验证码发送过于频繁,请稍后再试");}}

在发送验证码前调用:

@OverridepublicvoidsendCode(Stringphone){checkSendCodeLimit(phone);Stringcode=String.valueOf(newRandom().nextInt(900000)+100000);Stringkey="login:code:"+phone;stringRedisTemplate.opsForValue().set(key,code,5,TimeUnit.MINUTES);System.out.println("验证码:"+code);}

3. 文字说明

这段代码核心是:

Longcount=stringRedisTemplate.opsForValue().increment(key);

每请求一次,计数加 1。

如果是第一次请求:

if(count!=null&&count==1){stringRedisTemplate.expire(key,60,TimeUnit.SECONDS);}

设置 60 秒过期。

当 60 秒内请求次数超过 5 次:

thrownewRuntimeException("验证码发送过于频繁,请稍后再试");

这就是一个简单固定窗口限流。

九、Redis key 命名规范

项目中 Redis key 不要随便起名。

不推荐:

code123 user1 p1001

推荐:

login:code:13800000000 product:1001 article:1001:readCount limit:sendCode:13800000000

命名建议:

业务模块:业务对象:唯一标识:属性

比如:

article:1001:readCount

表示文章 1001 的阅读量。

好处是:

  1. 可读性强
  2. 方便排查问题
  3. 方便按业务分类
  4. 避免 key 冲突

十、Redis 使用中的常见问题

1. 缓存对象要不要设置过期时间?

一般建议设置。

比如商品详情缓存:

.set(key,json,30,TimeUnit.MINUTES)

如果不设置过期时间,缓存可能长期占用内存。

2. 空值缓存过期时间为什么要短?

因为数据库中未来可能新增这条数据。

如果空值缓存时间太长,新增后用户可能还一直查不到。

所以空值缓存一般设置短一点,比如 1 到 5 分钟。

3. 更新数据库后 Redis 怎么办?

常见做法是:

先更新数据库,再删除缓存

比如:

productMapper.update(product);stringRedisTemplate.delete("product:"+product.getId());

下一次查询时再重新加载缓存。

4. 为什么不直接更新缓存?

直接更新缓存也可以,但复杂对象可能出现一致性问题。

很多项目更倾向于更新数据库后删除缓存,让下次查询自动重建。

十一、全局异常处理补充

前面代码中使用了:

thrownewRuntimeException("验证码发送过于频繁,请稍后再试");

实际项目中更推荐自定义业务异常:

publicclassBusinessExceptionextendsRuntimeException{publicBusinessException(Stringmessage){super(message);}}

然后配合全局异常处理:

@RestControllerAdvicepublicclassGlobalExceptionHandler{@ExceptionHandler(BusinessException.class)publicResulthandleBusinessException(BusinessExceptione){returnResult.error(e.getMessage());}@ExceptionHandler(Exception.class)publicResulthandleException(Exceptione){returnResult.error("系统异常,请联系管理员");}}

这样接口返回更统一。

十二、面试标准回答

如果面试官问:

Spring Boot 项目中怎么使用 Redis?

可以这样回答:

Spring Boot 中可以引入spring-boot-starter-data-redis依赖,然后在配置文件中配置 Redis 的 host、port、password、database 等信息。

项目中常用StringRedisTemplate操作 Redis,适合保存字符串、JSON、验证码、token、计数器等数据。比如验证码可以使用opsForValue().set(key, code, 5, TimeUnit.MINUTES)保存并设置过期时间;商品详情可以先查 Redis,命中就返回,未命中再查 MySQL,并把结果序列化成 JSON 写入 Redis;阅读量可以使用increment实现自增。

使用 Redis 时要注意 key 命名规范、设置合理过期时间、防止缓存穿透、更新数据库后删除缓存,以及不要存储过大的 value。

十三、常见问题总结

1. Spring Boot 操作 Redis 常用哪个类?

常用:

StringRedisTemplate

也可以使用:

RedisTemplate

学习和常规业务中,StringRedisTemplate更直观。

2. 对象怎么存 Redis?

通常把对象序列化成 JSON 字符串存入 Redis。

读取时再反序列化成对象。

3. 验证码为什么适合放 Redis?

因为验证码是临时数据,并且需要过期时间。

Redis 天然支持 key 过期。

4. 阅读量为什么适合放 Redis?

因为 Redis 支持原子自增,性能高,适合高频计数。

5. 更新数据库后缓存怎么处理?

常见做法是:

更新数据库后删除缓存

下次查询时重新加载缓存。

十四、实际开发建议

Spring Boot 整合 Redis 时,可以注意下面几点:

  1. 使用清晰的 key 命名规范
  2. 临时数据一定设置过期时间
  3. 缓存对象建议设置合理 TTL
  4. 对象缓存可以使用 JSON 字符串
  5. 数据库查询为空时可以短时间缓存空值
  6. 更新数据库后删除缓存
  7. 不要把超大对象放入 Redis
  8. 计数器可以使用 Redis 自增命令
  9. 限流可以使用 Redis 自增加过期时间
  10. 复杂分布式锁建议用 Redisson

十五、总结

这一篇主要学习了 Spring Boot 项目整合 Redis 的常见用法。

我们先引入 Redis 依赖,配置 Redis 地址,然后使用StringRedisTemplate操作 Redis。

文章中实现了几个常见场景:验证码保存和校验、商品详情缓存、阅读量自增、简单接口限流。

这些功能基本覆盖了 Redis 在 Java 后端项目中的基础用法。真正项目中,Redis 不只是一个缓存工具,还可以承担临时数据存储、计数、限流、分布式锁等职责。

到这里,Redis 八股与实战系列的 10 篇文章就形成了一个完整闭环:从 Redis 基础概念,到数据类型,再到缓存问题、持久化、高可用和项目整合。

http://www.jsqmd.com/news/1191703/

相关文章:

  • C++跨平台剪贴板操作:纯头文件实现与多系统API详解
  • 从《论新阶段》到“活的灵魂”:毛泽东思想核心要义与当代技术创新的思想启迪
  • C++ WinInet实现高效HTTP文件下载:断点续传与多线程优化
  • 2026年7月最新嘉兴美度官方售后客服电话及服务网点地址查询 - 亨得利钟表维修中心
  • WorkBuddy 连接百度网盘:能查能上传,但别指望 AI 帮你下载
  • AWR1843BOOST毫米波雷达在自动泊车系统中的优势与实现
  • 影刀RPA 影刀中的SQL查询大全:从简单查询到多表联查
  • Midscene.js框架(5):测试报告系统与调试技巧
  • 第二章 温度、热量与能量(四)---焓
  • 为什么你的Claude脚本总要人工重写?深度拆解Prompt熵值与代码可执行性之间的3个数学阈值(基于217个真实case统计)
  • 从GESP五级真题解析C++算法思维:有趣的数字和问题
  • 模型监控仪表盘实战:从数据漂移到服务延迟的五维健康监测
  • Cocos Creator 3.8.6 游戏快速上架微信小游戏全流程实战指南
  • 2026工作总结工具哪家好用?5款AI工具实测对比,告别无效熬夜写总结
  • 【电路实战】MOS管缓启动电路的设计要点与参数计算
  • C++20 Ranges实战:filter与transform高效用法与性能优化
  • TI CC2640R2L蓝牙5.1 MCU:超低功耗物联网设计全解析
  • 影刀RPA 影刀流程的单元测试方法:不靠试跑靠验证
  • MSP430F676x1A外设配置与低功耗设计实战指南
  • GPT-4o原生语音架构解析:多模态实时交互的技术本质
  • Matlab实战:数据预处理的完整流程与核心技巧
  • 从流到帧:拆解TCP粘包拆包与HTTP/1.1的边界协议设计
  • TI TPS6594-Q1 ESM模块深度解析:电平与PWM模式配置及功能安全实践
  • LangChain--07智能体----Agent的基本用法
  • C++多线程数据竞争:从检测工具到修复模式的完整指南
  • 热成像技术开发实战:从原理到Python代码实现
  • C++标识符与关键字详解:命名规则、编译原理与最佳实践
  • STC8H高级定时器实现PWM移相与动态占空比控制
  • 国际半导体展会哪家好?全球供应链对接,行业展会汇总 - 品牌深度评测
  • 夏斐临摹:AI代码生成从记忆到理解的技术突破