结论:spring boot用redistemplate存复杂对象不配序列化器,90%概率存为乱码、取为null或classcastexception;因默认jdkserializationredisserializer生成不可读二进制字节流(如\xac\xed\x00\x05),且跨语言不互通、反序列化易失败,必须显式配置stringredisserializer(key)和jackson2jsonredisserializer(value),并注册javatimemodule支持localdatetime等类型。

直接说结论:Spring Boot用 RedisTemplate 存复杂对象(比如含嵌套、日期、泛型字段的DTO),不配对序列化器,90%概率存进去是乱码、取出来是 null 或 ClassCastException。
为什么 redisTemplate.opsForValue().set("key", obj) 存进去看不到?
因为默认用的是 JdkSerializationRedisSerializer,它把整个对象序列化成二进制字节流,Redis CLI 或图形化客户端里看到的就是一串不可读的 \xac\xed\x00\x05...。这不是数据丢了,是“人看不懂”,但程序反序列化时也容易失败——尤其跨服务或升级 JDK 后。
- 必须显式配置 key 和 value 的序列化器,不能依赖自动配置的默认值
-
StringRedisSerializer适合 key(纯字符串),也适合简单 value(如 String、Long) - value 是复杂对象时,优先选
Jackson2JsonRedisSerializer或GenericJackson2JsonRedisSerializer,后者支持泛型擦除后的反序列化 - 如果用了 Lombok 的
@Data+ Jackson,记得加@JsonIgnore排除循环引用字段,否则序列化直接抛StackOverflowError
get("key") 返回 null,但 redis-cli 能查到这个 key?
这是典型的“存取模板不一致”问题。Spring Boot 自动配置会生成两个 Bean:RedisTemplate<object object></object>(默认)和 StringRedisTemplate。如果你用 StringRedisTemplate 存,却用注入的 RedisTemplate 取,或者反过来,序列化器不匹配,反序列化失败就静默返回 null。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 检查注入的变量类型:是
RedisTemplate<string object></string>还是StringRedisTemplate?二者序列化策略不同,不能混用 - 不要靠 IDE 自动导入,手动写明泛型:用
@Autowired RedisTemplate<string object> redisTemplate;</string> - 如果只操作字符串类 value,统一用
StringRedisTemplate,它默认 key/value 都是StringRedisSerializer,最省心 - 自定义
RedisTemplateBean 时,务必调用afterPropertiesSet(),否则部分序列化器不生效
存 LocalDateTime 报错 “Can not construct instance of java.time.LocalDateTime”?
Jackson 默认不认识 Java 8 时间类型,反序列化时找不到无参构造器,直接炸。
- 必须注册
JavaTimeModule:在Jackson2JsonRedisSerializer初始化时传入已配置好的ObjectMapper - 示例片段:
ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); Jackson2JsonRedisSerializer<object> serializer = new Jackson2JsonRedisSerializer(Object.class); serializer.setObjectMapper(mapper);</object>
- 避免用
GenericJackson2JsonRedisSerializer处理含时间字段的对象——它内部用的 ObjectMapper 没预装 JavaTimeModule - 更稳妥的做法:DTO 中时间字段统一用
String或Long,由业务层转换,绕过序列化陷阱
真正麻烦的不是怎么存,而是“什么时候该用 Hash 存字段、什么时候该用 String 存整个对象”。比如店铺状态这种单值开关,用 set("shop:1001:status", "OPEN") 就够了;但如果是带营业时间、公告、客服电话的完整店铺配置,用 Hash 拆字段存更利于局部更新和原子操作——这个边界得根据实际读写模式判断,不能光看对象大小。










