• Redission 使用Jackson处理LocalDateTime的一些坑


    @(redis)

    Redission 使用Jackson处理LocalDateTime的一些坑

    当我们想要Redission 存取 LocalDateTime类型的值时,会碰上一些坑,有些坑很浅,有些坑很深。

    不支持jsr310的问题(浅坑)

    准备

    不做任何处理,只是要求使用Jackson 来做序列号和反序列化的处理,代码如下:

    配置RedissonClient 的代码:

        private RedissonClient getRedissonClient(){
            Config config = new Config();
            config.useSingleServer().setAddress("redis://127.0.0.1:6379")
                .setDatabase(30)
            ;
            JsonJacksonCodec jacksonCodec = new JsonJacksonCodec();
            jacksonCodec.getObjectMapper()
                .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
            config.setCodec(jacksonCodec);
            return Redisson.create(config);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    测试代码:

        @Test
        public void testRedissonLocalDateTime(){
            LocalDateTime now = LocalDateTime.now();
            RedissonClient redissonClient = getRedissonClient();
            //默认编码器(Java序列化和反序列化)Bucket
            RBucket<LocalDateTime> voRBucket = redissonClient.getBucket("testLocalDateTime");
            voRBucket.set(now);
            System.out.println(voRBucket.get());
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    测试结果:

    java.lang.IllegalArgumentException: 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
    
    • 1

    问题

    发现这个问题了之后,搜索一下关键词很容易找到解决方案。Jackson缺少了jsr310的注册,所以补充这个就可以。

    解决

    修改配置RedissonClient 的代码,增加支持支持jsr310,如果没有引入对应依赖的,需要增加对应依赖的引入。

        private RedissonClient getRedissonClient(){
            Config config = new Config();
            config.useSingleServer().setAddress("redis://47.96.74.163:6379")
                .setDatabase(30)
            ;
            JsonJacksonCodec jacksonCodec = new JsonJacksonCodec();
            jacksonCodec.getObjectMapper()
                //补充支持jsr310
                .registerModule(new JavaTimeModule())
                .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
            config.setCodec(jacksonCodec);
            return Redisson.create(config);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    调整代码后,测试非常顺利,正确的存储了数据,Redis上的数据也正确,控制台上的数据也正确。代码可以生产使用了。

    读取 LocalDateTime类型的值的问题(深坑)

    准备

    在上面的代码中,我们通过System.out.println(voRBucket.get()); 代码读取了存储在LocalDateTime的值,结果输出和我们预想的一样,看Redis上的数据保存的值也是符合预期的。但当我们将读取的代码调整下的时候,就会出现一个神奇的问题。

    调整后测试代码如下:

    	@Test
        public void testRedissonLocalDateTime2(){
            LocalDateTime now = LocalDateTime.now();
            RedissonClient redissonClient = getRedissonClient();
            //默认编码器(Java序列化和反序列化)Bucket
            RBucket<LocalDateTime> voRBucket = redissonClient.getBucket("testLocalDateTime");
            voRBucket.set(now);
            System.out.println(voRBucket.get());
    
            //取出LocalDateTime 对象,再输出
            LocalDateTime localDateTime = voRBucket.get();
            System.out.println(localDateTime);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    测试结果如下:

    java.lang.ClassCastException: java.lang.String cannot be cast to java.time.LocalDateTime
    
    • 1

    问题

    新增加的LocalDateTime localDateTime = voRBucket.get(); 这行代码抛出异常了,异常的内容很直白,但处理却有点麻烦。因为我们肯定是支持了jsr310,肯定是支持StringLocalDateTime的,而且我们也通过泛型告诉了它,这里要转换成LocalDateTime,但这里好像很傻,它并不知道应该要做转换,而只是用了Java的强转,所以抛异常了。

    但是为什么System.out.println(voRBucket.get()); 这里获取的时候没有抛异常呢?都是get方法,难道还能不一样?

    实际情况确实是不一样的,之System.out.println(voRBucket.get()); 这个方法不会抛异常,因为它压根就没做LocalDateTime转换,因为System.out.println 调用的是Object 类型的参数方法,String直接向上转型为Object 是不用调转换方法的,因此不需要转换。

    解决

    因此我们需要明确告知Redisson ,将返回的结果帮我转换成LocalDateTime对象,因此我们获取Bucket的时候,就得告知它,我们Bucket的值得用什么Codec来转换。于是我们找到了TypedJsonJacksonCodec 这个Jackson 专用的Codec, 通过它构建一个Codec,改进后代码如下:

        @Test
        public void testRedissonLocalDateTime3(){
            LocalDateTime now = LocalDateTime.now();
            RedissonClient redissonClient = getRedissonClient();
            //使用指定的编码器Bucket
            TypedJsonJacksonCodec localDateTimeCodec = new TypedJsonJacksonCodec(new TypeReference<LocalDateTime>() {});
            RBucket<LocalDateTime> voRBucket = redissonClient.getBucket("testLocalDateTime",localDateTimeCodec);
            voRBucket.set(now);
            System.out.println(voRBucket.get());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行结果:

    java.lang.IllegalArgumentException: 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
    
    • 1

    这个问题又出现了,这个时候可能会想配置RedissonClient 的时候,不是已经加了jsr310的支持么?为什么这里还有问题?一切的问题很其实很清晰,看下TypedJsonJacksonCodec 的构造方法就知道。

        public TypedJsonJacksonCodec(TypeReference<?> valueTypeReference) {
            this(valueTypeReference, new ObjectMapper());
        }
    
    • 1
    • 2
    • 3

    很明显,这个构造方法,默认new了一个新的ObjectMapper,而不是用我们之前配置的ObjectMapper。因此我们在构建TypedJsonJacksonCodec 时,需要将配置的ObjectMapper也传入。最终解决问题的代码:

        @Test
        public void testRedissonLocalDateTime4(){
            LocalDateTime now = LocalDateTime.now();
            RedissonClient redissonClient = getRedissonClient();
            //使用指定的编码器Bucket
            JsonJacksonCodec jacksonCodec = (JsonJacksonCodec)redissonClient.getConfig().getCodec();
            TypedJsonJacksonCodec localDateTimeCodec = new TypedJsonJacksonCodec(new TypeReference<LocalDateTime>() {},
                jacksonCodec.getObjectMapper());
            RBucket<LocalDateTime> voRBucket = redissonClient.getBucket("testLocalDateTime",localDateTimeCodec);
            voRBucket.set(now);
            System.out.println(voRBucket.get());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当然更好的写法是,将TypedJsonJacksonCodec localDateTimeCodec的定义成静态的

        private static final JsonJacksonCodec jacksonCodec = new JsonJacksonCodec(JsonUtils.getRedisOm());
        private static final TypedJsonJacksonCodec localDateTimeValueCodeC = new TypedJsonJacksonCodec(
            new TypeReference<LocalDateTime>() {},jacksonCodec.getObjectMapper());
    
    
    • 1
    • 2
    • 3
    • 4

    总结

    其实这里最大坑就是我们平时会用System.out.println来验证我们预期,但没有很容易忽视这个方法是被重载了多次的,并且Jackson做序列化时,对于泛型是不是指定一个接受的泛型类型就可以的,必须得明确指定泛型的类型,这个通常用TypeReference 这个接口的匿名类来处理。当没有指定泛型的类型时,Jackson默认是转换成Object类型来处理的,而对于System.out.println来说,Object类型它会默认调用toString方法来输出,而如果这个Object类型本身就是String强转过来的,那么输出的就是String本身了。在这个问题里,LocalDateTime 保存的字符串和我们取出来的字符串并输出的是一样的,因此只看控制台输出,它们是一样的。但如果我们用对象去接就会发现,默认返回不是LocalDateTime类型的对象,而是一个String对象。

  • 相关阅读:
    Linux学习 -- shell工具的复习(cut/sed/awk/sort)
    MATLAB环境下滚动轴承复合故障仿真信号及时频谱
    优雅的实现EasyPoi动态导出列的两种方式
    【C++初阶】C++STL详解(四)—— vector的模拟实现
    BIO和NIO消耗的cpu和内存比较
    软件杯 深度学习YOLO图像视频足球和人体检测 - python opencv
    Vue+Koa+MongoDB从零打造一个任务管理系统
    doris通关之概念、架构篇
    机器学习笔记之贝叶斯线性回归(二)推断任务推导过程
    将C语言中的命名格式改为Java中的驼峰式命名
  • 原文地址:https://blog.csdn.net/weixin_48990070/article/details/133862365