专栏名称: Java基基
一个苦练基本功的 Java 公众号,所以取名 Java 基基
目录
相关文章推荐
中国人民银行  ·  中国人民银行召开2025年货币金银和安全保卫 ... ·  13 小时前  
同心大城小事网络科技  ·  【关注】最高30万!@宁夏人 举报这些行为有奖→ ·  昨天  
防骗大数据  ·  网络兼职需谨慎,莫让诈骗找上门 ·  昨天  
中国能源报  ·  智利停电最新进展→ ·  2 天前  
中国能源报  ·  智利停电最新进展→ ·  2 天前  
光明网  ·  泰国突发!17人死亡、40多人受伤 ·  2 天前  
51好读  ›  专栏  ›  Java基基

RedisTemplate 的序列化策略和配置处理

Java基基  · 公众号  ·  · 2024-12-13 11:55

主要观点总结

本文描述了一个社群交流平台的部分功能和使用中遇到的问题,以及对应的解决方案。涉及内容包括报错、序列化策略、项目背景等。

关键观点总结

关键观点1: 报错信息解析

描述了在两个项目中因为使用不同的序列化策略而导致反序列化时缺少类型信息,从而引发报错。

关键观点2: 序列化策略解析

介绍了Jackson、GenericJackson和FastJson三种序列化策略的特点和适用场景。

关键观点3: 解决方案介绍

提供了三种解决方案:统一使用一种序列化策略、使用objectMapper移除@type、创建新的RedisTemplate实例使用不同的序列化策略。


正文

👉 这是一个或许对你有用 的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入 芋道快速开发平台 知识星球。 下面是星球提供的部分资料:

👉 这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:

  • Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本

来源:blog.csdn.net/shenlf_bk
/article/details/140846100


一、背景

有三个项目:

项目A通过redis 存放传输记录

redis序列化策略使用: FastJson2JsonRedisSerializeri ,若新增数据时数据中包含@type

项目B负责更新redis中的传输记录

redis序列化策略使用: Jackson2JsonRedisSerializer ,若新增数据时数据中包含@class

1. 报错:

由于使用FastJson进行的序列化,使用的 Jackson 进行反序列化时,反序列化时缺少类型信息

这行代码报错:

UserNfsTransferRecord record = (UserNfsTransferRecord) redisTemplate.opsForHash().get(redisKey, key);

报错信息:

org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Could not resolve subtype of [simple type, class java.lang.Object]: missing type id property '@classat [Source: (byte[])"{"@type":"com.ly.docker.userins.domain.UserNfsTransferRecord","creatTime":"2024-07-30 16:37:11","fileKey":"b1eff874-568d-4dbd-9b3c-af6c169d0bca8687","fileName":"头像3.webp","fileSize":11596,"isValid":1,"regionId":8,"status":2,"updateTime":"2024-07-30 16:37:11","userId":8687}"; line: 1, column: 277]; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, class java.lang.Object]: missing type id property '@class' at [Source: (byte[])"{"@type":"com.ly.docker.userins.domain.UserNfsTransferRecord","creatTime":"2024-07-30 16:37:11","fileKey":"b1eff874-568d-4dbd-9b3c-af6c169d0bca8687","fileName":"头像3.webp","fileSize":11596,"isValid":1,"regionId":8,"status":2,"updateTime":"2024-07-30 16:37:11","userId":8687}"; line: 1, column: 277] at org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer.deserialize(Jackson2JsonRedisSerializer.java:75) at org.springframework.data.redis.core.AbstractOperations.deserializeHashValue(AbstractOperations.java:380) at org.springframework.data.redis.core.DefaultHashOperations.get(DefaultHashOperations.java:57) at com.ly.service.UserNfsTransferRecordService.getOneRecord(UserNfsTransferRecordService.java:67) UserNfsTransferRecord retrievedRecord = (UserNfsTransferRecord) redisTemplate.opsForHash().get(redisKey, fileKey);

2. redis3种序列化策略

Jackson2JsonRedisSerializer GenericJackson2JsonRedisSerializer 都是 Spring Data Redis 中用于序列化和反序列化的工具,它们的主要区别如下:

1. Jackson
  • 库: 使用 Jackson 库。
  • 类型安全: Jackson2JsonRedisSerializer 在序列化和反序列化时需要指定目标类型。
  • 代码示例: Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class);
  • 使用场景: 更适合用于类型已知的场景,序列化和反序列化都需要知道具体的 Java 类型。
  • 缺点: 对多类型对象处理不便,需要显式指定类型。

数据举例:

{"@class":"com.ly.pojo.domain.UserNfsTransferRecord","regionId":8,"userId":1842,"fileName":"node-v14.21.3-x64 (7).zip","fileSize":29266106,"fileKey":"d0132f3d-3051-4e60-816d-3dc18b99c56f1842","status":2,"creatTime":"2024-07-23 16:11:40","updateTime":"2024-07-23 16:11:40","isValid":1
2. GenericJackson
  • 库: 使用 Jackson 库。
  • 泛型支持: GenericJackson2JsonRedisSerializer 可以处理任意类型的对象,而不需要在每次序列化和反序列化时指定类型。
  • 代码示例: GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
  • 使用场景: 更加通用,适合于不确定对象类型或者需要处理多种类型对象的场景。
  • 优点: 更加通用,适合处理多种类型对象的场景。
  • 缺点: 序列化数据中包含类型信息,增加了一些开销。

数据举例:

{"@class":"com.ly.pojo.domain.UserNfsTransferRecord","regionId"




    
:8,"userId":1842,"fileName":"node-v14.21.3-x64 (7).zip","fileSize":29266106,"fileKey":"d0132f3d-3051-4e60-816d-3dc18b99c56f1842","status":2,"creatTime":"2024-07-23 16:11:40","updateTime":"2024-07-23 16:11:40","isValid":1}
3. FastJson
  • 库: 使用 Fastjson 库。
  • 性能: Fastjson 通常比 Jackson 更快,尤其在序列化和反序列化大批量数据时性能更好。
  • 代码示例: FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer<>(Object.class);
  • 优点: 性能较好,序列化和反序列化速度较快。
  • 缺点: 安全性问题,Fastjson 曾经暴露出一些安全漏洞;而且社区维护和更新频率较低。
 {"@type":"com.ly.docker.userins.domain.UserNfsTransferRecord","creatTime":"2024-07-30 17:08:45","fileKey":"04aa2c2d-0ceb-43e2-a5fc-713feabe57e31842","fileName":"Feige.dmg","fileSize":15407187,"isValid":1,"regionId":8,"status":2,"updateTime":"2024-07-30 17:08:45","userId":1842

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

二、解决方案:

写在前面:报错是在反序列化时出错的,两个项目的实体类的地址还不一样, GenericJackson 和Jackson解析的都是@class。其实只用在反序列化时移除@type即可

1. 统一使用一种序列化策略

使用 Jackson GenericJackson FastJson 种的一种进行序列化和反序列化

弊端:两个项目的实体类的地址需要一样

2. 使用objectMapper移除@type

忽略未知属性: 通过配置 ObjectMapper ,忽略未知属性。(这个方法修改的是RedisConfig需要统一序列化策略)

手动移除 @type 字段: 在反序列化之前,手动移除 @type 字段。

自动忽略@type字段: 通过配置 ObjectMapper ,忽略未知属性(不是修改的RedisConfig)

3. 解决步骤

我在不影响现有 Jackson 序列化配置的情况下新增一个基于 GenericJackson 的 Redis 配置,可以创建一个新的 RedisTemplate 实例,使用 GenericJackson 作为序列化器。这样可以在项目中同时使用不同的序列化策略,而不相互干扰。

PS:实际上不用新增,直接用原来的就行,这里我没弄清楚,可忽略

@Configuration
@EnableCaching
@Slf4j
public class RedisConfig extends CachingConfigurerSupport {
 
    @Bean
    @Primary //在原有方法上新增这个注解,原始代码默认调用这个redisTemplate
    public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        serializer.setObjectMapper(mapper);
 
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(serializer);
 
        // Hash的key也采用StringRedisSerializer的序列化方式
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
 
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
 
    @Bean(name = "generiRedisTemplate"//新增的generiRedisTemplate策略配置
    public RedisTemplate generiRedisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
 
        ObjectMapper objectMapper = new ObjectMapper();
        // Configure ObjectMapper if needed
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(objectMapper);
 
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(serializer);
 
        // Hash的key也采用StringRedisSerializer的序列化方式
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
 
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

要确保你使用的是 generiRedisTemplate 而不是 redisTemplate ,可以通过以下几种方法来调用和区分不同的 RedisTemplate 实例

可以通过 @Qualifier 注解来区分和注入不同的 RedisTemplate 实例。

1) 在服务类中注入特定的 RedisTemplate 实例

@Bean 注解

默认行为是使用方法名作为 Bean 名称。因此,确保你为每个 RedisTemplate 实例的方法指定了不同的名称,以避免 Bean 名称冲突。如果需要,可以显式指定 Bean 名称,如下所示:

@Bean(name = "generiRedisTemplate")
public RedisTemplate generiRedisTemplate(RedisConnectionFactory connectionFactory) {
    RedisTemplate redisTemplate = new RedisTemplate<>();
    //........
}

@Qualifier 注解

区分和注入不同的 RedisTemplate 实例。首先,为每个 RedisTemplate 实例定义一个唯一的 Bean 名称。

@Autowired
@Qualifier("generiRedisTemplate")
private RedisTemplate generiRedisTemplate;

@Primary 注解

当有多个 Bean 实现了相同的接口(例如 RedisTemplate ), @Primary 注解用于指定默认的 Bean。如果一个 Bean 被标记为 @Primary ,Spring 会优先注入这个 Bean。

如果你只需要一个主要的 RedisTemplate 实例而没有特殊的区分要求,可以使用 @Primary 注解来指定默认的 RedisTemplate 实例。然而,既然你希望同时支持多个 RedisTemplate 实例,并且需要明确区分它们, @Qualifier 是更合适的选择。

2 )移除@type

1. 忽略未知属性

在RedisConfig中配置。

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.DeserializationFeature;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
 
@Configuration
public class RedisConfig






请到「今天看啥」查看全文


推荐文章
同心大城小事网络科技  ·  【关注】最高30万!@宁夏人 举报这些行为有奖→
昨天
防骗大数据  ·  网络兼职需谨慎,莫让诈骗找上门
昨天
中国能源报  ·  智利停电最新进展→
2 天前
中国能源报  ·  智利停电最新进展→
2 天前
北京吃货小分队  ·  哦我不吃早午餐的,我只吃brunch!
8 年前
全球健身指南  ·  男朋友从不在朋友圈秀恩爱,竟是因为...
7 年前