前言
LocalDateTime
是 Java 8 中引入的日期时间 API,位于 java.time
包下,该包中的类是不可变且线程安全。
当使用 Redis
缓存 LocalDateTime
的数据时,会出现以下报错:
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.lcxuan.issues.core.pojo.User["createTime"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.13.5.jar:2.13.5]
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1300) ~[jackson-databind-2.13.5.jar:2.13.5]
at com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer.serialize(UnsupportedTypeSerializer.java:35) ~[jackson-databind-2.13.5.jar:2.13.5]
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) ~[jackson-databind-2.13.5.jar:2.13.5]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774) ~[jackson-databind-2.13.5.jar:2.13.5]
...
问题分析:使用 Redis 缓存有 LocalDateTime 类型的变量的类时会将类进行序列化,而 Jackson 库默认情况下是无法对 LocalDateTime 类型的变量进行序列化和反序列化操作。
Github 地址:https://github.com/Lcxuan/project-issues,如有帮助,欢迎 Star,感谢!!!
解决方案
从错误信息中给出了解决方案,需要添加 com.fasterxml.jackson.datatype:jackson-datatype-jsr310
的依赖。
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.5</version>
</dependency>
但是只添加 com.fasterxml.jackson.datatype:jackson-datatype-jsr310
依赖解决不了序列化的问题,还需要自定义 Redis 的序列化和反序列化操作。
1、自定义序列化和反序列化器
这里序列化时将 LocalDateTime 转换为时间戳,而反序列化时将时间戳转换 LocalDateTime。
自定义序列化 LocalDateTimeToTimestampSerializer
类,用于将 LocalDateTime 转换为时间戳,代码如下:
public class LocalDateTimeToTimestampSerializer extends JsonSerializer<LocalDateTime> {
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
long epochMilli = value.atZone(getDefaultTimeZone()).toInstant().toEpochMilli();
gen.writeString(String.valueOf(epochMilli));
}
private static ZoneId getDefaultTimeZone() {
return ZoneId.of("GMT+8");
}
}
自定义反序列化 TimestampToLocalDateTimeDeserializer
类,反序列化时将时间戳转换 LocalDateTime,代码如下:
public class TimestampToLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
String timestampStr = p.getText();
return LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(timestampStr)), getDefaultTimeZone());
}
private static ZoneId getDefaultTimeZone() {
return ZoneId.of("GMT+8");
}
}
2、自定义序列化配置
自定义 Redis 的配置,代码如下:
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 创建 RedisTemplate 对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用 String 序列化方式,序列化 KEY 。
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
template.setValueSerializer(buildRedisSerializer());
template.setHashValueSerializer(buildRedisSerializer());
return template;
}
/**
* 构建 Redis 序列化器
*/
private static RedisSerializer<?> buildRedisSerializer() {
RedisSerializer<Object> json = RedisSerializer.json();
// 解决 LocalDateTime 的序列化
ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(LocalDateTime.class, new LocalDateTimeToTimestampSerializer());
simpleModule.addDeserializer(LocalDateTime.class, new TimestampToLocalDateTimeDeserializer());
objectMapper.registerModule(simpleModule);
// 解决其他时间序列化
objectMapper.registerModules(new JavaTimeModule());
return json;
}
}
测试代码
创建 User 实体类,用于将该实体类缓存到 Redis 中,代码如下:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private LocalDateTime createTime;
}
创建 TestController 类,用于编写操作 Redis 功能代码,代码如下:
@RequestMapping("/issues01")
@RestController
public class TestController {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@PostMapping("test01")
public Object test01() {
User user = new User(1001, "张三", LocalDateTime.now());
redisTemplate.opsForValue().set("issues01:test01", user, 60L, TimeUnit.SECONDS);
return redisTemplate.opsForValue().get("issues01:test01");
}
}
启动主程序,这里使用 Postman 来请求 API 接口,访问地址:localhost:10000/issues01/test01,Postman 结果如下界面:
Redis 缓存结果如下界面: