• LocalDateTime在项目中的使用(LocalDateTime对接前端通过时间戳互转、LocalDateTime对接数据库)


    1. 博客编写背景

    本文章的编写背景:由于在 JDK 8 中,Date、Timestamp 对象已经不推荐使用,所以在公司的新项目上,我计划将 LocalDateTime 使用在新项目中。

    由于本人所在项目组,统一的前后端时间的交互方式为时间戳,而时间戳并不能直接被fastJsonjackson 直接转换,所以踩了不少的坑,个人建议有耐心的看完。

    实现的效果如下:

    1. 前端传递时间戳
    {
    	"localDateTime": 1584700466000
    }
    
    1. 后端返回时间戳
    {
        "code": "0",
        "desc": "请求成功",
        "data": {
            "localDateTime": 1584700466000
        }
    }
    

    ========================================================

    若是感觉废话比较多,那么直接看标注了【★★★】的即可
    个人写这个博客,并不想直接写结论,更多的是想给读者分享踩坑的过程

    ========================================================


    2. LocalDateTime 前端交互

    2.1 LocalDateTime 向前端写入时间戳

    2.1.1 fastJson 默认的写入格式

    本项目使用的是 fastJson 会写前端,我们先看下以下代码

    1. 回写前端的 VO 对象
    @Data
    public class LocalDateTimeVO {
        private LocalDateTime localDateTime;
    }
    
    1. 测试方法
    public static void main(String[] args) {
        LocalDateTimeVO localDateTimeVO = new LocalDateTimeVO();
        localDateTimeVO.setLocalDateTime(LocalDateTime.now());
        String json = JSON.toJSONString(localDateTimeVO);
        System.out.println(json);
    }
    
    1. 控制台输出
    {"localDateTime":"2020-03-12T23:00:28.747"}
    

    从上图中可以看出,服务端并不能正常的返回时间戳给前端。并不符合需求。

    2.1.2 更改 fastJson 写入格式,让其回写时间戳 (★★★)

    1. fastJson提供了自定义 json 转换的方法 @JSONFiled,我们添加 serializeUsing,将其指定到我们自定义的序列化控制器即可。
    2. 自定义 fastJson 序列化转换器,重写 ObjectSerializer
    /**
     * 由于 LocalDateTime 类型在转换 JSON 的时候,并不能被转换为字符串,使用 @JsonFormat 只能转换为指定的 pattern 类型,因此我们需要自定义一个序列化执行器
     * LocalDateTime 序列化(将 LocalDateTime类型 转换为 时间戳 返回给前端 )
     *
     * @author Chimm Huang
     * @date 2020/3/7
     */
    public class LocalDateTimeSerializer implements ObjectSerializer {
        @Override
        public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
            if (object != null) {
                LocalDateTime localDateTime = (LocalDateTime) object;
                //将localDateTime转换为中国区(+8)时间戳。
                serializer.write(localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
            } else {
                serializer.write(null);
            }
        }
    }
    
    1. 使用我们自己写的 fastJson 序列化转换器
    @Data
    public class LocalDateTimeVO {
        @JSONField(serializeUsing = LocalDateTimeSerializer.class)
        private LocalDateTime localDateTime;
    }
    
    1. 再次执行测试方法,控制台输出
    {"localDateTime":1584026032912}
    

    可以看出,LocalDateTime 已经成功被转换为了时间戳,并且可以返回给前端。

    2.2 接收前端传递的时间戳为 LocalDateTimme

    2.2.1 Post 请求参数封装

    1. LocalDateTime 默认接收的格式

    不管我们传递时间戳(1584026032912),还是传递自定义格式("2020-03-13"),在服务端接受的时候,都会报错400。也就是说,传入的格式是错误的,无法被 spring 转换为 LocalDateTime

    经过我的粗略测试,我发现,默认的接受格式为 LocalDateTime 特有的格式,即:2020-03-12T23:00:28.747,除此之外都会报400。这种格式与 Date 格式的唯一区别就在于,Date之间是用空格区分的,而 LocalDateTime 是用 T 来区分的。

    2. 更改 fastJson 反序列化方法,让其能够转换时间戳为 LocalDateTime(★★★)

    1. fastJson 提供的 @JSONField 注解包括了反序列化转换器的指定,因此,我们重写其方法 ObjectDeserializer
    /**
     * 由于 时间戳 并不能直接被 fastJSON 转换为 LocalDateTime 类型,因此我们需要自定义一个序列化执行器
     * LocalDateTime 反序列化(将前端传递的 时间戳 转换为 LocalDateTime 类型)
     *
     * @author Chimm Huang
     * @date 2020/3/7
     */
    public class LocalDateTimeDeserializer implements ObjectDeserializer {
    
        @Override
        @SuppressWarnings("unchecked")
        public LocalDateTime deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
    
            String timestampStr = parser.getLexer().numberString();
    
            if (timestampStr == null || "".equals(timestampStr)) {
                return null;
            }
    
            timestampStr = timestampStr.replaceAll(""", "");
    
            long timestamp = Long.parseLong(timestampStr);
            if(timestamp == 0) {
                return null;
            }
            return Instant.ofEpochMilli(timestamp).atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
        }
    
        @Override
        public int getFastMatchToken() {
            return 0;
        }
    }
    
    1. 使用我们自己写的 fastJson 反序列化转换器
    @Data
    pubcli class LocalDateTimeVO {
        @JSONField(serializeUsing = LocalDateTimeSerializer.class, deserializeUsing = LocalDateTimeDeserializer.class)
        private LocalDateTime localDateTime;
    }
    
    1. 测试方法
    public static void main(String[] args) {
        String json = "{"localDateTime":1584026032912}";
        LocalDateTimeVO localDateTimeVO = JSON.parseObject(json, LocalDateTimeVO.class);
        System.out.println(localDateTimeVO);
    }
    
    1. 控制台执行结果展示
    LocalDateTimeVO(localDateTime=2020-03-12T23:13:52.912)
    

    可以看出,时间戳成功被 fastJson 接受,并转换为了 LocalDateTime

    3. 【坑】更改 SpringBoot 的 @RequestBody 为 fastJson 接收(★★★)

    当你看到这个小标题时,肯定会很疑惑,我们项目目前不就是使用的 fastJson
    吗?
    实际情况经过我测试,得出的结论是,我们在回写前端的时候,是使用 fastJson 进行转换的,但是在接受 Json 的时候,是使用 Spring 默认的 jackson 来接受的,所以这会导致,我们重写了 fastJson 的反序列化方法并未执行。前端传递时间戳给后端,后端报错400。


    因此,我们需要更改 spring 默认提供的 jacksonfastJson

    /**
     * springboot 默认使用的是 jackson 进行 requestBody 请求的封装,该项目切换为使用 fastJson 进行请求封装和响应
     * 配置 springboot 使用 fastJson 进行数据的请求接受和响应
     *
     * @author Chimm Huang
     * @date 2020/3/7
     */
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        public HttpMessageConverter<String> stringConverter() {
            return new StringHttpMessageConverter(StandardCharsets.UTF_8);
        }
    
        public FastJsonHttpMessageConverter fastConverter() {
            //1、定义一个convert转换消息的对象
            FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
            //2、添加fastJson的配置信息
            FastJsonConfig fastJsonConfig = new FastJsonConfig();
            fastJsonConfig.setSerializerFeatures(
                    SerializerFeature.WriteMapNullValue,
                    SerializerFeature.WriteNullStringAsEmpty,
                    SerializerFeature.WriteNullNumberAsZero,
                    SerializerFeature.WriteNullListAsEmpty,
                    SerializerFeature.WriteNullBooleanAsFalse);
    
            fastJsonConfig.setCharset(StandardCharsets.UTF_8);
            //2-1 处理中文乱码问题
            List<MediaType> fastMediaTypes = new ArrayList<>();
            fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
            fastConverter.setSupportedMediaTypes(fastMediaTypes);
            //3、在convert中添加配置信息
            fastConverter.setFastJsonConfig(fastJsonConfig);
            return fastConverter;
        }
    
        @Override
        public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
            converters.clear();
            converters.add(stringConverter());
            converters.add(fastConverter());
        }
    }
    

    配置完成之后,后端与前端使用时间戳进行交互已完成。

    2.2.2 GET 请求参数封装

    只需自定义一个转换类即可

    /**
     * LocalDateTime 作为 作为 RequestParam 或者 PathVariable 时,将前端传递的时间戳转换
     *
     * @author Chimm Huang
     * @date 2020/04/16
     */
    @Configuration
    public class LocalDateTimeGetConverter {
        @Bean
        public Converter<String, LocalDateTime> localDateTimeConverter() {
            return new Converter<String, LocalDateTime>() {
                @Override
                public LocalDateTime convert(String source) {
                    return LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(source)), ZoneId.systemDefault());
                }
            };
        }
    }
    

    实例代码:

    @GetMapping("/soutTime")
    public void soutTimeByGet(LocalDateTime localDateTime) {
        System.out.println(localDateTime);
    }
    

    前端请求:

    localhost:8001/api/demo/soutTime?localDateTime=1587026916000
    

    控制台输出:

    2020-04-16T16:48:36
    

    3. LocalDateTime 与数据库交互(★★★)

    与数据库交互比较简单,我们使用的 mybatis 的版本为 3.4.5。且数据库时间类型为:datetime
    我们只需要在 pom 文件中引入 jsr310 坐标即可

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-typehandlers-jsr310</artifactId>
        <version>1.0.2</version>
    </dependency>
    

    3.1 【坑】数据库交互LocalDateTime被四舍五入(★★★)

    LocalDateTime 是可以精确到纳秒的,但是数据库datetime类型如果不指定长度的话,默认是精确到秒的。这就会造成,在LocalDateTime为最大值的时候,如:2020-04-01T23:59:59.999999999,存入数据库的时候被四舍五入为了2020-04-02 00:00:00

    解决方案一:
    重置一下LocalDateTime的最大时间,将最大精度设置为秒。

    解决方案二:
    将数据库的datetime类型长度设置为6(datetime(6)即微秒),然后将LocalDateTime的最大精度重置为对应的微妙即可。

    以上两种方案调用LocalDateTimewithNano()方法即可

    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime todayMax = LocalDateTime.of(now.toLocalDate(), LocalTime.MAX);
        // 输出当天的时间
        System.out.println(now);
        // 输出当天的最大时间(默认最大)
        System.out.println(todayMax);
        // 输出当天的最大时间(datetime精度为秒的时候)
        System.out.println(todayMax.withNano(0));
        // 输出当天的最大时间(datetime精度为毫秒的时候) datetime(3)
        System.out.println(todayMax.withNano(999000000));
        // 输出当天的最大时间(datetime精度为微秒的时候) datetime(6)
        System.out.println(todayMax.withNano(999999000));
    }
    

    控制台输出

    2020-04-01T09:50:46.830845400
    2020-04-01T23:59:59.999999999
    2020-04-01T23:59:59
    2020-04-01T23:59:59.999
    2020-04-01T23:59:59.999999
    
  • 相关阅读:
    第一、二章读书笔记
    # 学号 20191221 《Python程序设计》实验一报告
    20191221实验四实验报告
    学号:20191221,《python实验设计》实验报告三
    20191221实验二报告
    快速浏览教材遇到
    何应霆 20191221
    2019-2020-1 20191319 《信息安全专业导论》第3周学习总结
    2019-2020-1 20191319《信息安全专业导论》第二周学习总结
    师生关系
  • 原文地址:https://www.cnblogs.com/chimmhuang/p/12695823.html
Copyright © 2020-2023  润新知