1. Spring Data Redis 简介
Spring Data Redis 是 Spring Data 家族的一部分,它为 Spring 应用提供了对 Redis 存储的高级抽象。它屏蔽了底层连接库(如 Jedis 或 Lettuce)的复杂实现细节,使开发者能够通过统一的 API 与 Redis 进行交互。
核心价值
- 连接管理:自动管理 Redis 连接的生命周期及连接池。
- 操作封装:将 Redis 原始命令封装为面向对象的 API。
- 异常转化 :将底层的 Redis 异常转换为 Spring 统一的
DataAccessException体系。
2. RedisTemplate 介绍
RedisTemplate 是 Spring Data Redis 的核心类,它提供了执行 Redis 操作的模板方法。
数据结构操作映射
RedisTemplate 将 Redis 的基本数据结构划分为不同的操作接口:
| 接口方法 | 对应的 Redis 数据结构 |
|---|---|
opsForValue() |
String (字符串) |
opsForHash() |
Hash (哈希) |
opsForList() |
List (列表) |
opsForSet() |
Set (集合) |
opsForZSet() |
ZSet (有序集合) |
3. Spring Boot 集成步骤
在 Spring Boot 3.4.2 环境下,集成步骤如下:
3.1 引入依赖 (pom.xml)
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
3.2 配置连接信息 (application.yaml)
yaml
spring:
data:
redis:
host: 127.0.0.1
port: 6379
database: 0
jedis:
pool:
max-active: 8 # 最大连接数
max-idle: 8 # 最大空闲连接
4. RedisTemplate 常见操作示例
4.1 String 字符串操作
java
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void testString() {
// 写入数据
redisTemplate.opsForValue().set("user:name", "czl");
// 读取数据
String value = (String) redisTemplate.opsForValue().get("user:name");
// 带过期时间的写入
redisTemplate.opsForValue().set("code", "1234", Duration.ofMinutes(5));
}
4.2 Hash 哈希操作
java
public void testHash() {
String key = "user:info:1";
// 存入单个字段
redisTemplate.opsForHash().put(key, "name", "tiger");
// 获取单个字段
Object name = redisTemplate.opsForHash().get(key, "name");
// 获取全部字段
Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
}
4.3 List 列表操作
java
public void testList() {
String key = "queue:task";
// 从左侧推入
redisTemplate.opsForList().leftPush(key, "task1");
// 从右侧弹出
Object task = redisTemplate.opsForList().rightPop(key);
}
4.4 Set 集合操作
java
public void testSet() {
String key = "tags";
// 添加元素
redisTemplate.opsForSet().add(key, "java", "redis", "spring");
// 判断是否存在
Boolean isMember = redisTemplate.opsForSet().isMember(key, "java");
}
5. RedisTemplate 与 Jedis 实例关系
在 Spring Boot 应用中,RedisTemplate 与底层 Jedis 实例之间并非一比一的绑定关系,而是一种典型的"多对一"或"一对多"的解耦模型。
5.1 实例数量对比
一对多关系
- RedisTemplate 。
在一个 Spring Context 中,你通常只需要配置一个RedisTemplate<String, Object>就能在全工程中通过@Autowired共享。它是无状态的,专门负责定义操作逻辑和序列化规则。不过也可以配置多个定制化RedisTemplate,关键是与Jedis实例是一对多关系。 - Jedis 实例:是一个连接池 。
底层通过JedisPool维护了多个长连接实例。具体数量由你在application.yaml中配置的max-active决定。
5.2 动态映射关系
当你调用 redisTemplate.opsForValue().set(...) 时,发生的数量调度如下:
- 借用阶段 :
RedisTemplate拦截到请求,从底层的JedisPool中申请一个 当前的空闲Jedis实例。 - 独占阶段 :在命令执行期间,这个特定的
Jedis实例被该线程独占,其他线程无法使用它。 - 归还阶段 :操作完成后,
RedisTemplate自动释放连接,将该Jedis实例归还给池中。
5.3 结论
- 逻辑层 :你只需要维护 1 个
RedisTemplate即可处理所有的并发请求。 - 物理层 :底层的
JedisPool会根据并发量动态分配 N 个Jedis实例来支持这 1 个模板的工作。
6. RedisTemplate 序列化器深度解析
6.1 为什么要使用序列化器?
Redis 数据库本身是二进制安全 的,它只能存储字节序列(byte array)。而 Java 是面向对象的语言,我们操作的是 String、Integer 或自定义的 UserDO 对象。
序列化器(Serializer)的作用:就是充当 Java 对象与 Redis 字节流之间的"翻译官"。
- 存入时:将 Java 对象转换成二进制字节。
- 读取时:将二进制字节转换回 Java 对象。
示例:不配置序列化器的后果
假设你使用 Spring 默认的 RedisTemplate(未手动配置序列化器),执行以下操作:
java
redisTemplate.opsForValue().set("name", "虎哥");
虽然代码运行成功,但当你通过命令行或 Redis Insight 查看时,你会发现:
- Key 变成了:
\xac\xed\x00\x05t\x00\x04name - Value 变成了:
\xac\xed\x00\x05t\x00\x06\xe8\x99\x8e\xe5\x93\xa5
这是因为默认使用了 JdkSerializationRedisSerializer,它在数据前添加了 Java 序列化协议的魔数(\xac\xed)和类元数据。这导致数据在 Redis 中不可读,且其他语言(如 Python、Go)无法解析。
6.2 序列化器的具体用法
在 Spring Boot 3.4.2 环境下,我们通过自定义 RedisConfig 类来指定序列化规则。
1. 配置代码示例
通常建议 Key 使用 String 序列化 ,Value 使用 JSON 序列化,以达到最佳的可读性和兼容性。
java
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 1. 指定 Key 的序列化器:StringRedisSerializer
// 效果:存入 "name",Redis 中显示为字符串 "name"
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// 2. 指定 Value 的序列化器:GenericJackson2JsonRedisSerializer
// 效果:存入对象,Redis 中显示为标准 JSON 字符串
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
2. 配置后的存储效果对比
在使用上述配置后,同样的 set("name", "虎哥") 操作,Redis 中的表现如下:
| 存储部分 | 默认 Jdk 序列化(乱码) | 配置后(明文/JSON) |
|---|---|---|
| Key | \xac\xed\x00\x05t\x00\x04name |
name |
| Value | \xac\xed\x00\x05t\x00... |
"虎哥" |
| 对象 Value | 二进制乱码 | {"id":1, "nickname":"虎哥"} |
7. Jackson 序列化器的内存占用问题及优化方案
7.1 Jackson 序列化器的"多余"字节码问题
在使用 GenericJackson2JsonRedisSerializer 时,为了实现自动反序列化,Jackson 会在生成的 JSON 字符串中额外添加一个名为 @class 的字段。
示例:自动序列化的存储结果
假设存储一个简单的 UserDO 对象:
json
{
"@class": "cn.iocoder.boot.springdataredis.UserDO",
"id": 1,
"nickname": "虎哥"
}
- 问题所在 :这个
@class字段包含了类全路径名,在大型项目中,这个字符串往往比业务数据本身还要长。 - 内存影响:如果你有数百万个 Key,这些重复的类路径字符串会额外占用大量的 Redis 内存空间,增加硬件成本和网络带宽压力。
7.2 解决方案:手动序列化(String + ObjectMapper)
为了追求极致的内存利用率,业界常用的方案是:全局只使用 StringRedisTemplate(或 String 序列化器),在代码逻辑中手动利用 ObjectMapper(或 JsonUtils)进行对象转换。
1. 核心思路
- 存储时 :调用
JsonUtils.toJsonString(obj)得到纯净的 JSON,不带@class标记,存入 Redis。 - 读取时 :取出 String,根据业务需要调用
JsonUtils.parseObject(json, UserDO.class)还原对象。
2. 代码实现示例
基于你已有的 Spring Boot 3.4.2 环境 和 JsonUtils:
java
@Service
public class UserService {
@Autowired
private StringRedisTemplate stringRedisTemplate; // 默认已有的 String 模板
public void saveUserManual(UserDO user) {
// 1. 手动序列化为纯净 JSON 字符串
String jsonStr = JsonUtils.toJsonString(user);
// 2. 存入 Redis
stringRedisTemplate.opsForValue().set("user:" + user.getId(), jsonStr);
}
public UserDO getUserManual(Long id) {
// 3. 获取纯净 JSON 字符串
String jsonStr = stringRedisTemplate.opsForValue().get("user:" + id);
// 4. 手动反序列化,通过 Class 参数明确目标类型
return JsonUtils.parseObject(jsonStr, UserDO.class);
}
}