前言前面 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 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency如果项目中要做 JSON 序列化可以使用 JacksondependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-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 中常用两个模板RedisTemplateStringRedisTemplate1. StringRedisTemplateStringRedisTemplate的 key 和 value 默认都是字符串。适合操作字符串JSON 字符串token验证码计数器比如stringRedisTemplate.opsForValue().set(name,zhangsan);2. RedisTemplateRedisTemplate更通用可以操作对象。但默认序列化方式如果没配置好可能会出现乱码或不易读的问题。学习和项目初期更推荐先使用StringRedisTemplate对象可以手动转成 JSON 字符串保存。这种方式可读性更好也更容易排查问题。四、创建统一 Result 返回类为了和前面的 Spring Boot 系列风格一致这里继续使用统一返回结果。publicclassResult{privateIntegercode;privateStringmsg;privateObjectdata;publicResult(){}publicResult(Integercode,Stringmsg,Objectdata){this.codecode;this.msgmsg;this.datadata;}publicstaticResultsuccess(){returnnewResult(1,success,null);}publicstaticResultsuccess(Objectdata){returnnewResult(1,success,data);}publicstaticResulterror(Stringmsg){returnnewResult(0,msg,null);}// getter、setter 省略}实际项目中也可以使用泛型版ResultT。五、案例一保存验证码1. 功能需求用户输入手机号请求发送验证码。后端生成 6 位验证码保存到 Redis并设置 5 分钟过期。key 设计login:code:手机号比如login:code:13800000000value 保存验证码9527132. Controller 层RestControllerRequestMapping(/code)publicclassCodeController{AutowiredprivateCodeServicecodeService;PostMapping(/send)publicResultsendCode(RequestParamStringphone){codeService.sendCode(phone);returnResult.success();}PostMapping(/check)publicResultcheckCode(RequestParamStringphone,RequestParamStringcode){booleanresultcodeService.checkCode(phone,code);if(result){returnResult.success(验证码正确);}returnResult.error(验证码错误或已过期);}}3. Service 层publicinterfaceCodeService{voidsendCode(Stringphone);booleancheckCode(Stringphone,Stringcode);}实现类ServicepublicclassCodeServiceImplimplementsCodeService{AutowiredprivateStringRedisTemplatestringRedisTemplate;OverridepublicvoidsendCode(Stringphone){StringcodeString.valueOf(newRandom().nextInt(900000)100000);Stringkeylogin:code:phone;stringRedisTemplate.opsForValue().set(key,code,5,TimeUnit.MINUTES);System.out.println(验证码code);}OverridepublicbooleancheckCode(Stringphone,Stringcode){Stringkeylogin:code:phone;StringredisCodestringRedisTemplate.opsForValue().get(key);returncode!nullcode.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:10012. 商品实体类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){Stringkeyproduct:id;StringjsonstringRedisTemplate.opsForValue().get(key);if(json!null!json.isEmpty()){try{returnobjectMapper.readValue(json,Product.class);}catch(JsonProcessingExceptione){thrownewRuntimeException(商品缓存解析失败,e);}}ProductproductproductMapper.getById(id);if(productnull){stringRedisTemplate.opsForValue().set(key,,2,TimeUnit.MINUTES);returnnull;}try{StringproductJsonobjectMapper.writeValueAsString(product);stringRedisTemplate.opsForValue().set(key,productJson,30,TimeUnit.MINUTES);returnproduct;}catch(JsonProcessingExceptione){thrownewRuntimeException(商品缓存序列化失败,e);}}}5. Controller 层RestControllerRequestMapping(/products)publicclassProductController{AutowiredprivateProductServiceproductService;GetMapping(/{id})publicResultgetById(PathVariableLongid){ProductproductproductService.getById(id);if(productnull){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:readCount2. Service 代码ServicepublicclassArticleService{AutowiredprivateStringRedisTemplatestringRedisTemplate;publicLongincrReadCount(LongarticleId){Stringkeyarticle:articleId:readCount;returnstringRedisTemplate.opsForValue().increment(key);}publicStringgetReadCount(LongarticleId){Stringkeyarticle:articleId:readCount;returnstringRedisTemplate.opsForValue().get(key);}}3. Controller 代码RestControllerRequestMapping(/articles)publicclassArticleController{AutowiredprivateArticleServicearticleService;PostMapping(/{id}/read)publicResultread(PathVariableLongid){LongcountarticleService.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:138000000002. Service 代码publicvoidcheckSendCodeLimit(Stringphone){Stringkeylimit:sendCode:phone;LongcountstringRedisTemplate.opsForValue().increment(key);if(count!nullcount1){stringRedisTemplate.expire(key,60,TimeUnit.SECONDS);}if(count!nullcount5){thrownewRuntimeException(验证码发送过于频繁请稍后再试);}}在发送验证码前调用OverridepublicvoidsendCode(Stringphone){checkSendCodeLimit(phone);StringcodeString.valueOf(newRandom().nextInt(900000)100000);Stringkeylogin:code:phone;stringRedisTemplate.opsForValue().set(key,code,5,TimeUnit.MINUTES);System.out.println(验证码code);}3. 文字说明这段代码核心是LongcountstringRedisTemplate.opsForValue().increment(key);每请求一次计数加 1。如果是第一次请求if(count!nullcount1){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 的阅读量。好处是可读性强方便排查问题方便按业务分类避免 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 时可以注意下面几点使用清晰的 key 命名规范临时数据一定设置过期时间缓存对象建议设置合理 TTL对象缓存可以使用 JSON 字符串数据库查询为空时可以短时间缓存空值更新数据库后删除缓存不要把超大对象放入 Redis计数器可以使用 Redis 自增命令限流可以使用 Redis 自增加过期时间复杂分布式锁建议用 Redisson十五、总结这一篇主要学习了 Spring Boot 项目整合 Redis 的常见用法。我们先引入 Redis 依赖配置 Redis 地址然后使用StringRedisTemplate操作 Redis。文章中实现了几个常见场景验证码保存和校验、商品详情缓存、阅读量自增、简单接口限流。这些功能基本覆盖了 Redis 在 Java 后端项目中的基础用法。真正项目中Redis 不只是一个缓存工具还可以承担临时数据存储、计数、限流、分布式锁等职责。到这里Redis 八股与实战系列的 10 篇文章就形成了一个完整闭环从 Redis 基础概念到数据类型再到缓存问题、持久化、高可用和项目整合。