一、前言:为什么你的 Redis 里全是乱码?
你是否遇到过这样的场景?
- 在 Redis Desktop Manager 中看到
key是\xac\xed\x00\x05t\x00\x03foo? - 存入一个 Java 对象,读出来却报
ClassCastException? - 不同服务之间无法共享 Redis 数据?
根本原因:序列化器(Serializer)配置不当!
Spring Data Redis 默认使用 JDK 自带的序列化机制,虽然"开箱即用",但存在:
- ❌ 可读性差(二进制乱码)
- ❌ 跨语言不兼容(如 Node.js 无法解析)
- ❌ 冗余信息多(包含类全路径,体积大)
本文将带你全面掌握 Spring Data Redis 的序列化体系,并教你如何配置最适合项目的序列化方案!
二、Spring Data Redis 的序列化架构
在 RedisTemplate 中,有 4 个关键序列化器:
| 序列化器 | 作用 | 默认实现 |
|---|---|---|
keySerializer |
序列化 Key(字符串) | JdkSerializationRedisSerializer |
valueSerializer |
序列化 Value(对象) | JdkSerializationRedisSerializer |
hashKeySerializer |
序列化 Hash 的 field | JdkSerializationRedisSerializer |
hashValueSerializer |
序列化 Hash 的 value | JdkSerializationRedisSerializer |
⚠️ 问题根源 :默认全部使用
JdkSerializationRedisSerializer,导致 Key/Value 都变成二进制!
三、常见序列化器对比
1. JdkSerializationRedisSerializer(默认)
- ✅ 无需额外配置
- ❌ 输出不可读(如
\xac\xed\x00\x05...) - ❌ 依赖 Java 类结构,跨版本/跨服务易出错
- ❌ 体积大(包含类元信息)
java
// 存入 "user:1" -> 实际 Key 在 Redis 中是二进制
redisTemplate.opsForValue().set("user:1", user);
2. StringRedisSerializer(推荐用于 Key 和 String Value)
- ✅ 输出纯字符串,可读性强
- ✅ 跨语言兼容
- ✅ 体积小
- ❌ 仅支持 String 类型(不能直接存对象)
💡 最佳实践 :所有 Key 必须用
StringRedisSerializer!
3. Jackson2JsonRedisSerializer(推荐用于对象 Value)
- ✅ JSON 格式,人类可读
- ✅ 跨语言通用
- ✅ 支持复杂嵌套对象
- ⚠️ 需处理泛型类型擦除(见下文)
4. GenericJackson2JsonRedisSerializer(增强版 Jackson)
- ✅ 自动写入
@class字段,解决反序列化类型丢失问题 - ✅ 无需手动指定类型
- ❌ JSON 中多出
@class字段(轻微冗余)
四、实战:配置优雅的序列化方案
场景:存储 User 对象,Key 为字符串,Value 为 JSON
步骤 1:定义实体类
java
public class User {
private String id;
private String name;
private int age;
// 构造函数、getter/setter 略
}
步骤 2:自定义 RedisTemplate 配置
java
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 1. Key 使用 String 序列化(必须!)
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// 2. Value 使用 Jackson JSON 序列化
Jackson2JsonRedisSerializer<Object> jacksonSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 解决 JDK8 时间类型(LocalDateTime 等)
objectMapper.registerModule(new JavaTimeModule());
// 忽略未知属性
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 允许非标准 JSON(如单引号)
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
jacksonSerializer.setObjectMapper(objectMapper);
template.setValueSerializer(jacksonSerializer);
template.setHashValueSerializer(jacksonSerializer);
template.afterPropertiesSet();
return template;
}
}
步骤 3:测试效果
java
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Test
void testSaveUser() {
User user = new User("1001", "张三", 25);
redisTemplate.opsForValue().set("user:1001", user, 1, TimeUnit.HOURS);
}
✅ Redis 中实际存储:
Key: user:1001 ← 纯字符串!
Value: {"id":"1001","name":"张三","age":25} ← 可读 JSON!
🎉 告别乱码,跨服务也能直接读取!
五、高级技巧:解决泛型反序列化问题
问题:List<User> 反序列化成 List<LinkedHashMap>
原因:Java 泛型擦除,Jackson 不知道具体类型。
解决方案 1:使用 TypeReference
java
// 存储
List<User> users = Arrays.asList(user1, user2);
redisTemplate.opsForValue().set("users:list", users);
// 读取(正确方式)
List<User> result = (List<User>) redisTemplate.execute(
(RedisCallback<List<User>>) connection -> {
byte[] bytes = connection.get("users:list".getBytes());
if (bytes == null) return Collections.emptyList();
try {
return new ObjectMapper().readValue(bytes,
new TypeReference<List<User>>() {});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
);
解决方案 2:使用 GenericJackson2JsonRedisSerializer
java
// 替换 Jackson2JsonRedisSerializer 为 Generic 版本
GenericJackson2JsonRedisSerializer genericSerializer =
new GenericJackson2JsonRedisSerializer();
template.setValueSerializer(genericSerializer);
template.setHashValueSerializer(genericSerializer);
此时存储的 JSON 会包含 @class 字段:
java
{
"@class": "com.example.User",
"id": "1001",
"name": "张三"
}
反序列化时自动识别类型,无需手动指定!
✅ 推荐 :中小型项目用
GenericJackson2JsonRedisSerializer,省心!
六、特殊场景:只操作字符串?用 StringRedisTemplate!
如果你只存字符串、数字、JSON 字符串 ,直接使用 Spring 提供的 StringRedisTemplate:
java
@Autowired
private StringRedisTemplate stringRedisTemplate;
// 存 JSON 字符串(手动序列化)
stringRedisTemplate.opsForValue().set("user:1001",
new ObjectMapper().writeValueAsString(user));
✅ 优点:零配置,默认 Key/Value 都是 String,无乱码风险!
七、避坑指南:常见序列化问题
❌ 坑 1:Key 出现乱码
原因 :未设置
keySerializer = StringRedisSerializer
后果 :无法通过命令行或可视化工具查找数据
解决 :所有项目必须显式配置 Key 为 String 序列化
❌ 坑 2:LocalDateTime 序列化失败
解决 :注册
JavaTimeModule
javaobjectMapper.registerModule(new JavaTimeModule());
❌ 坑 3:不同模块的 User 类路径不同,反序列化失败
场景 :A 服务存,B 服务读,但包名不同
解决:
- 方案 1:使用
GenericJackson2JsonRedisSerializer+ 统一 DTO- 方案 2:禁用
@class,改用 Map 手动转换
八、性能与安全建议
| 建议 | 说明 |
|---|---|
| Key 永远用 String | 保证可读性和兼容性 |
| 避免存储大对象 | 单 Value 建议 < 10KB |
| 敏感数据加密 | 如手机号、身份证,存前加密 |
| 统一序列化策略 | 全团队使用同一套配置 |
九、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!