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
敏感数据加密 如手机号、身份证,存前加密
统一序列化策略 全团队使用同一套配置

九、结语

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

相关推荐
forestsea2 小时前
深入理解Redisson RLocalCachedMap:本地缓存过期策略全解析
redis·缓存·redisson
佛祖让我来巡山2 小时前
Redis 为什么这么快?——「极速快递站」的故事
redis·redis为什么快?
啦啦啦_99994 小时前
Redis-0-业务逻辑
数据库·redis·缓存
自不量力的A同学4 小时前
Redisson 4.2.0 发布,官方推荐的 Redis 客户端
数据库·redis·缓存
fengxin_rou4 小时前
[Redis从零到精通|第四篇]:缓存穿透、雪崩、击穿
java·redis·缓存·mybatis·idea·多线程
fengxin_rou5 小时前
黑马点评实战篇|第二篇:商户查询缓存
缓存
是阿楷啊5 小时前
Java大厂面试场景:音视频场景中的Spring Boot与微服务实战
spring boot·redis·spring cloud·微服务·grafana·prometheus·java面试
笨蛋不要掉眼泪6 小时前
Redis哨兵机制全解析:原理、配置与实战故障转移演示
java·数据库·redis·缓存·bootstrap
ALex_zry18 小时前
Redis Cluster 分布式缓存架构设计与实践
redis·分布式·缓存
乔江seven21 小时前
【Flask 进阶】3 从同步到异步:基于 Redis 任务队列解决 API 高并发与长耗时任务阻塞
redis·python·flask