Springboot2默认情况下使用lettuce框架访问Redis
先明确几个关键结论:Spring Boot 2.x 默认集成的 Redis 客户端为 Lettuce,并非 Jedis。当你在项目中添加 spring-boot-starter-data-redis 依赖后,底层将自动采用 Lettuce + commons-pool2 连接池方案。简单来说,引入依赖这一步,就是在告知 Spring 框架:即将启用 Redis,并使用默认的高性能配置。以下是 Maven 依赖配置示例,请务必同时引入 commons-pool2,否则连接池功能将无法正常使用。

org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2
在需要操作Redis的类中注入StringRedisTemplate实例
依赖配置完成后,如何使用呢?操作非常简单:在任何由 Spring 管理的 Bean 中直接注入 StringRedisTemplate 即可,Spring Boot 已经为其完成了自动配置。你仅需添加一行 @Autowired 注解,其余工作全部交给框架处理。参考以下代码:
@Autowired StringRedisTemplate stringRedisTemplate;
StringRedisTemplate基础操作详解
获取 StringRedisTemplate 实例后,最常用的场景之一是操作 Hash 数据结构。通过调用 opsForHash() 方法获取 HashOperations 实例,随后即可像操作 Map 一样进行增删改查。需要注意的是,put 方法在 key 已存在时会直接覆盖旧值,因此它兼具添加与更新两种功能。以下示例代码涵盖了 Hash 数据类型的大部分基础操作:
HashOperationshashOperations = stringRedisTemplate.opsForHash(); // 添加 hashOperations.put("user_hash", "zhangfei", "black face"); // 更新(put覆盖) hashOperations.put("user_hash", "zhangfei", "mangfu"); // 查询全部(entries) Map userMap = hashOperations.entries("user_hash"); // 查询所有 key Set userKeys = hashOperations.keys("user_hash"); // 查询所有 value List userValues = hashOperations.values("user_hash"); // 删除 hashOperations.delete("user_hash", "zhangfei3"); // 判断是否存在 Boolean aBoolean = hashOperations.hasKey("user_hash", "zhangfei");
封装StringRedisTemplate简化实际项目开发
在实际项目开发中,我们通常会将 Redis 操作封装为独立的 Controller 或 Service 层,以便于测试和代码复用。以下是一个完整的 HashController 示例,演示了如何通过 RESTful 接口对 Hash 数据结构执行添加、更新、查询和删除操作。特别说明:getAll() 方法被多个接口复用,有效避免了代码冗余。
@Controller
@RequestMapping("hash")
public class HashController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@ResponseBody
@RequestMapping("/add")
public Map add() {
HashOperations hashOperations = stringRedisTemplate.opsForHash();
hashOperations.put("user_hash", "zhangfei", "black face");
hashOperations.put("user_hash", "guanyu", "red face");
return getAll();
}
@ResponseBody
@RequestMapping("/update")
public Map update() {
HashOperations hashOperations = stringRedisTemplate.opsForHash();
Boolean exists = hashOperations.hasKey("user_hash", "zhangfei");
System.out.println(exists);
hashOperations.put("user_hash", "zhangfei", "mangfu"); // 覆盖旧值 = 更新
return getAll();
}
@ResponseBody
@RequestMapping("/all")
public Map all() {
return getAll();
}
@ResponseBody
@RequestMapping("/delete")
public Map delete() {
HashOperations hashOperations = stringRedisTemplate.opsForHash();
hashOperations.delete("user_hash", "zhangfei3");
return getAll();
}
public Map getAll() {
HashOperations hashOperations = stringRedisTemplate.opsForHash();
Map userMap = hashOperations.entries("user_hash");
Set userKeys = hashOperations.keys("user_hash");
List userValues = hashOperations.values("user_hash");
return userMap;
}
}
String字符串类型操作详解
String 是 Redis 中最基础的数据类型,但 StringRedisTemplate 提供的操作远不止 set 和 get。例如,setIfAbsent 可实现简易分布式锁,increment 支持原子性计数器操作,append 则可用于字符串拼接。以下方法演示了这些常用功能的具体用法:
private void testValue() {
ValueOperations value = stringRedisTemplate.opsForValue();
value.getOperations().delete("aaa");
value.getOperations().delete("bbb");
value.set("aaa", "123");
System.out.println(value.setIfAbsent("aaa", "123")); // key已存在则返回false
System.out.println(value.size("aaa")); // 返回字符串长度
System.out.println(value.get("aaa"));
value.append("aaa", "456"); // 追加
System.out.println(value.get("aaa"));
value.increment("bbb", 1); // 数值自增
System.out.println(value.get("bbb"));
value.multiGet(Arrays.asList("aaa", "bbb", "ccc"))
.stream().forEach(System.out::println);
}
Hash散列类型操作详解
Hash 在 Redis 中相当于一个内嵌的字典结构,非常适合存储对象类型数据(例如用户信息)。putIfAbsent 方法可避免覆盖已有字段,increment 方法能对 Hash 中的数值字段执行原子性自增操作。此外,entries、keys、values 方法可分别一次性获取全部或部分数据。以下是完整的操作示例:
private void testHash() {
HashOperations hash = stringRedisTemplate.opsForHash();
hash.getOperations().delete("hash1");
hash.put("hash1", "aaa", "111");
hash.put("hash1", "bbb", "222");
hash.put("hash1", "ccc", "333");
System.out.println(hash.size("hash1"));
hash.entries("hash1").forEach((k, v) -> System.out.println(k + "=" + v));
hash.keys("hash1").stream().forEach(System.out::println);
hash.values("hash1").stream().forEach(System.out::println);
System.out.println(hash.putIfAbsent("hash1", "aaa", "aaa")); // 已有key,返回false
hash.increment("hash1", "count", 1);
System.out.println(hash.get("hash1", "count"));
hash.increment("hash1", "count", 1);
System.out.println(hash.get("hash1", "count"));
hash.increment("hash1", "count", -1);
System.out.println(hash.get("hash1", "count"));
System.out.println(hash.hasKey("hash1", "amount"));
System.out.println(hash.get("hash1", "count"));
hash.delete("hash1", "count");
}
Set集合类型操作详解
Set 类型的核心特点是元素唯一且无序,适用于标签系统、社交关系等业务场景。difference(差集)、intersect(交集)、union(并集)是极为实用的集合运算方法。pop 用于随机弹出一个元素,randomMember 可随机查看元素但不移除,move 则能将元素从一个集合迁移至另一个集合。以下代码将这些操作串联演示:
private void testSet() {
SetOperations set = stringRedisTemplate.opsForSet();
set.getOperations().delete("set1");
set.getOperations().delete("set2");
set.add("set1", "111", "222", "333", "111"); // 重复元素会自动去重
set.add("set2", "222", "333", "444");
System.out.println(set.size("set1"));
System.out.println(set.members("set1"));
System.out.println(set.members("set2"));
System.out.println(set.difference("set1", "set2")); // set1中set2没有的
System.out.println(set.intersect("set1", "set2")); // 交集
System.out.println(set.union("set1", "set2")); // 并集
set.remove("set1", "111");
System.out.println(set.pop("set1")); // 随机弹出一个
System.out.println(set.randomMember("set2")); // 随机查看一个
System.out.println(set.isMember("set2", "444"));
set.move("set2", "444", "set1"); // 从set2移到set1
System.out.println(set.members("set1"));
}
List列表类型操作详解
List 类型是一个有序的链表结构,支持从左端或右端插入与弹出元素,非常适合实现消息队列或最新消息列表等场景。leftPush 从左侧插入元素,rightPush 从右侧插入元素,trim 可截取指定范围内的元素子集。特别提示:leftPushIfPresent 仅在 key 存在时执行插入操作,可避免在空列表上产生无效写入。参考以下示例:
private void testList() {
ListOperations list = stringRedisTemplate.opsForList();
list.getOperations().delete("list");
list.leftPush("list", "111");
list.leftPushIfPresent("list", "222"); // 此时key不存在,不会插入
list.rightPush("list", "333");
list.rightPushIfPresent("list", "444");
System.out.println(list.index("list", 0));
System.out.println(list.range("list", 0, -1));
list.trim("list", 0, 2); // 只保留索引0~2的元素
System.out.println(list.range("list", 0, -1));
System.out.println(list.leftPop("list")); // 弹出并移除左侧第一个
System.out.println(list.rightPop("list"));
}
Zset有序集合类型操作详解
Zset 在 Set 的基础上引入了 score(分数)概念,支持按分数进行排序,特别适用于排行榜、优先级队列等场景。range 按分数升序排列元素,reverseRange 则按分数降序排列。rank 返回元素在集合中的索引位置(基于升序排序),score 可单独查询指定元素的分数。以下代码演示了这些核心操作:
private void testZSet() {
ZSetOperations zset = stringRedisTemplate.opsForZSet();
zset.getOperations().delete("zset1");
zset.getOperations().delete("zset2");
zset.add("zset1", "aaa", 1);
zset.add("zset1", "bbb", 1);
zset.add("zset1", "ccc", 1);
zset.add("zset2", "bbb", 1);
zset.add("zset2", "ccc", 1);
zset.add("zset2", "ddd", 1);
System.out.println(zset.size("zset1"));
System.out.println(zset.range("zset1", 0, -1));
zset.incrementScore("zset1", "bbb", 1);
zset.incrementScore("zset1", "aaa", 2);
System.out.println(zset.range("zset1", 0, -1));
zset.rangeWithScores("zset1", 0, -1).stream()
.forEach(t -> System.out.println(t.getValue() + "-" + t.getScore()));
System.out.println(zset.reverseRange("zset1", 0, -1));
zset.reverseRangeWithScores("zset1", 0, -1).stream()
.forEach(t -> System.out.println(t.getValue() + "-" + t.getScore()));
System.out.println(zset.rank("zset1", "ccc")); // 递增排序下的索引
System.out.println(zset.reverseRank("zset1", "ccc")); // 递减排序下的索引
System.out.println(zset.score("zset1", "bbb"));
zset.remove("zset2", "bbb");
System.out.println(zset.range("zset2", 0, -1));
}
实战案例:基于Redis Hash实现登录与Token管理
学习再多理论知识,都不如一个完整的实战案例更有价值。以下是一个基于 Redis Hash 数据结构存储 Token 与 RefreshToken 的登录、登出及刷新接口实现。核心设计思路如下:以 userId 作为 Hash 的 key,在其内部存储 token 和 refreshToken,并借助 Redis 的过期机制实现 Token 的自动失效。
package com.cjs.example.controller;
import com.cjs.example.ResponseResult;
import com.cjs.example.domain.LoginRequest;
import com.cjs.example.domain.LoginResponse;
import com.cjs.example.domain.RefreshRequest;
import com.cjs.example.enums.ResponseCodeEnum;
import com.cjs.example.utils.JWTUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.security.MD5Encoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import ja va.util.UUID;
import ja va.util.concurrent.TimeUnit;
@RestController
public class LoginController {
@Value("${secretKey:123456}")
private String secretKey;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@PostMapping("/login")
public ResponseResult login(@RequestBody @Validated LoginRequest request, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return ResponseResult.error(ResponseCodeEnum.PARAMETER_ILLEGAL.getCode(),
ResponseCodeEnum.PARAMETER_ILLEGAL.getMessage());
}
String username = request.getUsername();
String password = request.getPassword();
String userId = "01"; // 假设数据库查询得到
if ("hello".equals(username) && "world".equals(password)) {
String token = JWTUtil.generateToken(userId, secretKey);
String refreshToken = UUID.randomUUID().toString().replace("-", "");
HashOperations hashOperations = stringRedisTemplate.opsForHash();
// 以 userId 作为 key,内部存 token 和 refreshToken
String key = userId;
hashOperations.put(key, "token", token);
hashOperations.put(key, "refreshToken", refreshToken);
stringRedisTemplate.expire(key, JWTUtil.TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS);
LoginResponse loginResponse = new LoginResponse();
loginResponse.setToken(token);
loginResponse.setRefreshToken(refreshToken);
loginResponse.setUsername(userId);
return ResponseResult.success(loginResponse);
}
return ResponseResult.error(ResponseCodeEnum.LOGIN_ERROR.getCode(),
ResponseCodeEnum.LOGIN_ERROR.getMessage());
}
@GetMapping("/logout")
public ResponseResult logout(@RequestParam("userId") String userId) {
HashOperations hashOperations = stringRedisTemplate.opsForHash();
hashOperations.delete(userId);
return ResponseResult.success();
}
@PostMapping("/refreshToken")
public ResponseResult refreshToken(@RequestBody @Validated RefreshRequest request, BindingResult bindingResult) {
String userId = request.getUserId();
String refreshToken = request.getRefreshToken();
HashOperations hashOperations = stringRedisTemplate.opsForHash();
String originalRefreshToken = hashOperations.get(userId, "refreshToken");
if (StringUtils.isBlank(originalRefreshToken) || !originalRefreshToken.equals(refreshToken)) {
return ResponseResult.error(ResponseCodeEnum.REFRESH_TOKEN_INVALID.getCode(),
ResponseCodeEnum.REFRESH_TOKEN_INVALID.getMessage());
}
String newToken = JWTUtil.generateToken(userId, secretKey);
hashOperations.put(userId, "token", newToken);
stringRedisTemplate.expire(userId, JWTUtil.TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS);
return ResponseResult.success(newToken);
}
}
总结与核心要点回顾
以上内容全面介绍了 Spring Boot 中 StringRedisTemplate 操作 Redis 五种核心数据类型的详细用法,并附上了一个贴近真实业务场景的登录案例。从依赖配置到具体 API 调用,再到实战中的 Token 生命周期管理,涵盖了日常开发中最常用的 Redis 操作场景。希望这份技术资料能帮助你在实际项目中快速掌握 Redis 集成与开发技巧。
