Redis序列化LocalDateTime报错解决

前言

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 结果如下界面:

Postman 请求结果

Redis 缓存结果如下界面:

Redis 缓存界面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lcxuan27

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值