SpringDataRedis数据序列化器

一、前言:为什么你的 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

java 复制代码
objectMapper.registerModule(new JavaTimeModule());

❌ 坑 3:不同模块的 User 类路径不同,反序列化失败

场景 :A 服务存,B 服务读,但包名不同
解决

  • 方案 1:使用 GenericJackson2JsonRedisSerializer + 统一 DTO
  • 方案 2:禁用 @class,改用 Map 手动转换

八、性能与安全建议

建议 说明
Key 永远用 String 保证可读性和兼容性
避免存储大对象 单 Value 建议 < 10KB
敏感数据加密 如手机号、身份证,存前加密
统一序列化策略 全团队使用同一套配置

九、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

相关推荐
进击的CJR8 小时前
redis cluster 部署
java·redis·mybatis
s_daqing8 小时前
ubuntu(arm)安装redis
linux·redis·ubuntu
wangmengxxw8 小时前
SpringAi-Advisor续
redis·springai·advisor
RoboWizard11 小时前
8TB SSD还有掉速问题吗?
人工智能·缓存·智能手机·电脑·金士顿
*才华有限公司*12 小时前
# 彻底解决 RedisReadOnlyException:从「只读从节点」到「独立主节点」的实战指南
redis
成为你的宁宁13 小时前
【Zabbix 监控 Redis 实战教程(附图文教程):从 Zabbix-Server 部署、Agent2 安装配置到自带监控模板应用全流程】
数据库·redis·zabbix
数据安全科普王14 小时前
HTTP缓存机制详解:强缓存 vs 协商缓存
网络协议·http·缓存
墨雨晨曦8815 小时前
如何保证redis和mysql数据一致性方案对比
数据库·redis·mysql
超级种码15 小时前
Redis:Redis 常见问题及解决思路
数据库·redis·缓存