Redis中储存含LocalDateTime属性对象的序列化实现
目录
1.问题1 向Redis中存入序列化对象
1.1引入 :
1.2解决方案:
1.2.1首先引入依赖
1.2.2然后在RedisConfig中进行配置
1.3 介绍下ObjectMapper
1.3.1 ObjectMapper
1.3.2 objectMapper.registerModule(new JavaTimeModule());
1.3.3 GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
2 问题2 从Redis取出数据转换为Java对象
2.1 引入:
2.2 解决方案
3 序列化优点
4 objectMapper.convertValue()
4.1 原理
4.2 基本步骤
4.3 示例
1.问题1 向Redis中存入序列化对象
"Could not write JSON: Java 8 date/time type `java.time.LocalDateTime` not supported by default"
1.1引入 :
在mysql中存储时间除了TimeStamp以外,常用的就是就是DateTime了,若在表中包含DateTime字段,对应的Java实体类属性常使用LocalDateTime类。
LocalDateTime(包括LocalDate等常见类) 是 Java 8 中引入的新时间和日期 API 中的类,属于 java.time 包,它提供了更强大的日期时间操作功能。Java 8 之前的 java.util.Date 和 java.util.Calendar 类在设计上有许多局限性,而 Java 8 通过引入 java.time 包,提供了更加直观和线程安全的日期时间处理类。
但需要注意:
当类中包含 Java 8 的 LocalDateTime 类型属性时,无法直接将该类序列化为 JSON 并存储在 Redis 中,因为 LocalDateTime 是非标准的时间格式,JSON 序列化工具(例如 Jackson 或 Gson)无法直接处理这种类型。
若直接进行存储会出现如下错误:
"Could not write JSON: Java 8 date/time type `java.time.LocalDateTime` not supported by default......"
Could not write JSON: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: com.hspedu.seckill.pojo.User["registerDate"]); nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: com.hspedu.seckill.pojo.User["registerDate"])
1.2解决方案:
1.2.1首先引入依赖
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.12.3</version> <!-- 请根据你项目的 Jackson 版本选择合适的版本号 -->
</dependency>
1.2.2然后在RedisConfig中进行配置
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 设置Key序列化方式
template.setKeySerializer(new StringRedisSerializer());
// 设置HashKey序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
// 使用Jackson2JsonRedisSerializer来序列化和反序列化Redis的value
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
// 设置Value序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer);
// 设置HashValue序列化方式
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
完成配置后即可在直接将包含LocalDateTime属性的对象直接进行序列化存储在Redis中,
如redisTemplate.opsForValue().set(key,yourObject);的方式直接存储即可。
1.3 介绍下ObjectMapper
1.3.1 ObjectMapper
上面代码中配置 RedisTemplate 的序列化机制,以便能够序列化和反序列化包含 LocalDateTime 等 Java 8 时间类型的对象。我们详细分解下这几行代码的功能:
ObjectMapper objectMapper = new ObjectMapper();
• 作用:ObjectMapper 是 Jackson 库的核心类,用来将 Java 对象转换为 JSON 字符串,或者将 JSON 字符串解析为 Java 对象。
Jackson 是一个非常流行的 JSON 序列化和反序列化库,ObjectMapper 是它的主要接口,用于处理复杂的对象和数据结构。
1.3.2 objectMapper.registerModule(new JavaTimeModule());
• 作用:为 ObjectMapper 注册一个 JavaTimeModule 模块。
默认情况下,Jackson 不支持直接序列化和反序列化 Java 8 的日期时间类,如 LocalDateTime、LocalDate 等。如果直接使用 ObjectMapper 处理这些类型,会抛出异常,因为 Jackson 不知道如何正确处理这些类。
JavaTimeModule 是一个扩展模块,专门用于处理 Java 8 引入的日期和时间类型,比如 LocalDateTime、ZonedDateTime、LocalDate 等。通过注册这个模块,Jackson 就能够正确地序列化这些时间类型为 JSON,并在反序列化时也能将 JSON 转回到这些时间类型对象。
// 注册 JavaTimeModule 之后,Jackson 就可以处理 LocalDateTime 了
objectMapper.registerModule(new JavaTimeModule());
1.3.3 GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
• 作用:使用上面配置好的 ObjectMapper 创建一个 GenericJackson2JsonRedisSerializer 实例,作为 Redis 的序列化工具即可。
2 问题2 从Redis取出数据转换为Java对象
2.1 引入:
在从Redis中读取通过以上方式存储的序列化后的数据,假如你存入的是一个User 对象,User 有一个LocalDateTime类型的属性,你通过上面ObjectMapper序列化器存储到Redis中,若直接使用User user = (User) redisTemplate.opsForValue().get(key);则会出现如下错误:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.seckill.pojo.User
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.seckill.pojo.User
at com.seckill.service.impl.UserServiceImpl.getUserByCookie(UserServiceImpl.java:98)
at com.seckill.service.impl.UserServiceImpl$$FastClassBySpringCGLIB$$d92357c4.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
at com.seckill.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$16ffc976.getUserByCookie(<generated>)
at
2.2 解决方案
因为包含LocalDateTime等Java8中的新类,从Redis取数据时默认会返回一个LinkedHashMap,其中keys为属性名,values为属性值。因此需要使用ObjectMapper进行类型转换。
YourClass test = objectMapper.convertValue(redisTemplate.opsForValue().get(key),YourClass.class);
即可将获得的数据由LinkedHashMap转为对应的类别了。
3 序列化优点
1)Redis 是数据存储系统,而非 Java 对象存储系统:
• Redis 支持的基础数据类型有字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)等,无法直接理解和处理复杂的 Java 对象。
• 因此,必须将 Java 对象转化为 Redis 能够理解的格式,例如字符串或二进制字节流,这就需要序列化操作。
2)跨平台和跨语言的兼容性:
• Redis 是一个与语言无关的存储系统,支持多种语言的客户端(如 Java、Python、Go 等)。序列化将 Java 对象转换为通用格式,如 JSON、XML 或二进制流,使其可以在不同平台或语言之间互操作。
• 例如,JSON 序列化可以让其他语言的客户端读取 Java 系统存入 Redis 的数据。
3)Java 对象结构复杂:
• Java 对象不仅仅是基本数据类型,它可能包含嵌套的对象、集合、数组等复杂结构。这些复杂结构不能简单地转换为 Redis 可识别的字符串表示。
• 通过序列化,可以将整个对象结构转化为线性的、可传输的字节流,从而可以存储到 Redis。
4)性能优化:
• 序列化后的数据通常是二进制或字符串表示,这些格式的数据可以被 Redis 高效地存储和操作,提升 Redis 的读写性能。
• 例如,使用二进制序列化可以节省存储空间,同时减少网络传输时的带宽消耗。
4 objectMapper.convertValue()
4.1 原理
ObjectMapper.convertValue() 是 Jackson 库中的方法,用于将一个对象(通常是 Map 或者其他类型的对象)转换为另一种类型。其工作原理基于 Jackson 库强大的序列化和反序列化机制,背后的基本流程是:
1. 数据的中间表示:在内部,ObjectMapper 会先将输入对象(例如 LinkedHashMap 或其他对象)转换成一种中间表示(类似 JSON 数据的内部结构),这个中间表示可以是一个 Map、数组、字符串等。
2. 类型推断和映射:convertValue() 方法会根据你传入的目标类型(例如 YourClass.class),将中间表示映射到该目标类型的字段中。这个过程类似于反序列化的过程,即 Jackson 会查看目标类中的字段和类型信息,并试图将中间表示中的数据逐一填充到目标对象的对应字段。
3. 属性映射和类型转换:convertValue() 通过 Jackson 的反射机制读取目标类的属性,并将输入对象中的数据映射到这些属性上。在这个过程中,Jackson 能自动处理各种类型转换,包括基本类型、复杂对象、集合类型,甚至像 LocalDateTime 这样的自定义对象类型(前提是 Jackson 已经配置了对这些类型的支持,例如通过 JavaTimeModule 模块)。
4.2 基本步骤
当调用 convertValue(sourceObject, TargetClass.class) 时,基本流程如下:
• Step 1: 将 sourceObject 转换成内部的 JSON 树结构(例如 LinkedHashMap 等)。
• Step 2: 根据目标类 TargetClass 的结构,Jackson 查找和匹配相应的属性。
• Step 3: 将中间 JSON 树结构中的数据逐步填充到目标类的实例中。
• Step 4: 返回填充好的目标类对象。
4.3 示例
//假设 Redis 中存储的数据反序列化为 LinkedHashMap:
Map<String, Object> redisData = redisTemplate.opsForValue().get("someKey");
//这个 redisData 包含了 TestPojo 对象的所有字段和对应的值。然后通过 convertValue,可以将其转换为 TestPojo 对象:
YourClass yourClass = objectMapper.convertValue(redisData, YourClass.class);
//Jackson 将通过字段名称和类型推断,将 redisData 中的值映射到 YourClass 的字段中。