有三个项目:
项目A通过redis 存放传输记录
redis序列化策略使用:
FastJson2JsonRedisSerializeri
,若新增数据时数据中包含@type
项目B负责更新redis中的传输记录
redis序列化策略使用:
Jackson2JsonRedisSerializer
,若新增数据时数据中包含@class
由于使用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 '@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]; 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);
Jackson2JsonRedisSerializer
和
GenericJackson2JsonRedisSerializer
都是 Spring Data Redis 中用于序列化和反序列化的工具,它们的主要区别如下:
类型安全:
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
泛型支持:
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 }
性能:
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即可
使用
Jackson
、
GenericJackson
、
FastJson
种的一种进行序列化和反序列化
弊端:两个项目的实体类的地址需要一样
忽略未知属性: 通过配置
ObjectMapper
,忽略未知属性。(这个方法修改的是RedisConfig需要统一序列化策略)
手动移除 @type 字段: 在反序列化之前,手动移除 @type 字段。
自动忽略@type字段: 通过配置
ObjectMapper
,忽略未知属性(不是修改的RedisConfig)
我在不影响现有 Jackson 序列化配置的情况下新增一个基于
GenericJackson
的 Redis 配置,可以创建一个新的
RedisTemplate
实例,使用
GenericJackson
作为序列化器。这样可以在项目中同时使用不同的序列化策略,而不相互干扰。
PS:实际上不用新增,直接用原来的就行,这里我没弄清楚,可忽略
@Configuration @EnableCaching @Slf 4jpublic 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
实例。
@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
是更合适的选择。
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