首页 > 其他分享 >SpringBoot中使用LocalDateTime踩坑记录

SpringBoot中使用LocalDateTime踩坑记录

时间:2024-01-27 09:03:35浏览次数:27  
标签:SpringBoot 记录 MM dd LocalDateTime new 序列化 class

目录

    前言

    近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。

    本项目基于Java21和SpringBoot3开发,序列化工具使用的是默认的Jackson,使用Spring Data Redis操作Redis缓存。

    在定义实体类过程中,日期时间类型的属性我使用了java.time包下的LocalDateLocalDateTime类,而没有使用java.util包下的Date类。

    但在使用过程中遇到了一些问题,于是在此记录下来与诸位分享。

    一、为什么推荐使用java.time包的LocalDateTime而不是java.util的Date?

    LocalDateTime和Date是Java中表示日期和时间的两种不同的类,它们有一些区别和特点。

    • 类型:LocalDateTime是Java 8引入的新类型,属于Java 8日期时间API(java.time包)。而Date是旧版Java日期时间API(java.util包)中的类。
    • 不可变性:LocalDateTime是不可变的类型,一旦创建后,其值是不可变的,对该类对象的加减等计算操作不会修改原对象,而是会返回一个新的LocalDateTime对象。而Date是可变的类型,可以通过方法修改其值。
    • 线程安全性:LocalDateTime是线程安全的,多个线程可以同时访问和操作不同的LocalDateTime实例。而Date是非线程安全的,如果多个线程同时访问和修改同一个Date实例,可能会导致不可预期的结果。
    • 时间精度:LocalDateTime提供了纳秒级别的时间精度,可以表示更加精确的时间。而Date只能表示毫秒级别的时间精度。
    • 时区处理:LocalDateTime默认不包含时区信息,表示的是本地日期和时间。而Date则包含时区信息,它的实际值会受到系统默认时区的影响。

    由于LocalDateTime是Java 8及以上版本的新类型,并提供了更多的功能和灵活性,推荐在新的项目中使用LocalDateTime来处理日期和时间。

    对于旧版Java项目,仍然需要使用Date类,但在多线程环境下需要注意其线程安全性。

    如果需要在LocalDateTime和Date之间进行转换,可以使用相应的方法进行转换,例如通过LocalDateTime的atZone()方法和Date的toInstant()方法进行转换。

    二、使用LocalDateTime和LocalDate时遇到了哪些坑?

    2.1 Redis序列化报错

    2.1.1 问题现象

    在使用RedisTemplate向Redis中插入数据时,遇到了如下报错:

    2024-01-11T21:33:25.233+08:00 ERROR 13212 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
    

    org.springframework.data.redis.serializer.SerializationException: 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: java.util.ArrayList[0]->com.fast.alden.data.model.SysApiResource["createdTime"])
    at org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer.serialize(Jackson2JsonRedisSerializer.java:157) ~[spring-data-redis-3.2.0.jar:3.2.0]
    at org.springframework.data.redis.core.AbstractOperations.rawValue(AbstractOperations.java:128) ~[spring-data-redis-3.2.0.jar:3.2.0]
    at org.springframework.data.redis.core.DefaultValueOperations.set(DefaultValueOperations.java:236) ~[spring-data-redis-3.2.0.jar:3.2.0]

    image

    2.1.2 问题分析

    在使用Redis缓存含有LocalDateTime类型变量的实体类时会产生序列化问题,因为Jackson库在默认情况下不支持Java8的LocalDateTime类型的序列化和反序列化。

    错误堆栈中也给出了解决方案,添加 com.fasterxml.jackson.datatype:jackson-datatype-jsr310依赖,但光添加依赖是不够的,还我们需要自定义序列化和反序列化的行为。

    2.1.3 解决方案

    1. 添加maven依赖
    <dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-jsr310</artifactId>
      <version>2.13.0</version>
    </dependency>
    
    1. 修改RedisSerializer Bean配置

    在定义RedisSerializer Bean的代码中自定义ObjectMapper对象处理时间属性时的序列化和反序列化行为,LocalDateLocalDateTimeLocalTime的序列化和反序列化都要自定义,还要禁用将日期序列化为时间戳。

    @Configuration
    public class RedisConfig {
        @Bean
        public RedisSerializer<Object> redisSerializer() {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            // 必须设置,否则无法将JSON转化为对象,会转化成Map类型
            objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
    
        <span class="hljs-comment">// 自定义ObjectMapper的时间处理模块</span>
        <span class="hljs-type">JavaTimeModule</span> <span class="hljs-variable">javaTimeModule</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">JavaTimeModule</span>();
    
        javaTimeModule.addSerializer(LocalDateTime.class, <span class="hljs-keyword">new</span> <span class="hljs-title class_">LocalDateTimeSerializer</span>(DateTimeFormatter.ofPattern(<span class="hljs-string">"yyyy-MM-dd HH:mm:ss"</span>)));
        javaTimeModule.addDeserializer(LocalDateTime.class, <span class="hljs-keyword">new</span> <span class="hljs-title class_">LocalDateTimeDeserializer</span>(DateTimeFormatter.ofPattern(<span class="hljs-string">"yyyy-MM-dd HH:mm:ss"</span>)));
    
        javaTimeModule.addSerializer(LocalDate.class, <span class="hljs-keyword">new</span> <span class="hljs-title class_">LocalDateSerializer</span>(DateTimeFormatter.ofPattern(<span class="hljs-string">"yyyy-MM-dd"</span>)));
        javaTimeModule.addDeserializer(LocalDate.class, <span class="hljs-keyword">new</span> <span class="hljs-title class_">LocalDateDeserializer</span>(DateTimeFormatter.ofPattern(<span class="hljs-string">"yyyy-MM-dd"</span>)));
    
        javaTimeModule.addSerializer(LocalTime.class, <span class="hljs-keyword">new</span> <span class="hljs-title class_">LocalTimeSerializer</span>(DateTimeFormatter.ofPattern(<span class="hljs-string">"HH:mm:ss"</span>)));
        javaTimeModule.addDeserializer(LocalTime.class, <span class="hljs-keyword">new</span> <span class="hljs-title class_">LocalTimeDeserializer</span>(DateTimeFormatter.ofPattern(<span class="hljs-string">"HH:mm:ss"</span>)));
    
        objectMapper.registerModule(javaTimeModule);
        
        <span class="hljs-comment">// 禁用将日期序列化为时间戳的行为</span>
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        
        <span class="hljs-comment">//创建JSON序列化器</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Jackson2JsonRedisSerializer</span>&lt;&gt;(objectMapper, Object.class);
    }
    

    }

    2.2 LocalDateTime和LocalDate类型的属性返回给前端的值格式不正确

    2.2.1 问题现象

    在application.yml中设置了全局的日期类型的序列化和反序列化格式,在对应字段上也并没有使用@JsonFormat进行特殊设置,但是LocalDateTime类型的属性返回给前端时并没有生效,返回的仍是LocalDateTime默认的ISO标准时间格式的字符串。

    spring:
      jackson:
        date-format: yyyy-MM-dd HH:mm:ss
        time-zone: GMT+8
        default-property-inclusion: always
      mvc:
        format:
          date-time: yyyy-MM-dd HH:mm:ss
          date: dd/MM/yyyy
    

    image

    2.2.2 解决方案

    自定义Jackson配置,代码如下:

    @Configuration
    public class JacksonConfig {
        @Bean
        public Jackson2ObjectMapperBuilderCustomizer customizer() {
            return builder ->
                    builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss")
                            // long类型转string, 前端处理Long类型,数值过大会丢失精度
                            .serializerByType(Long.class, ToStringSerializer.instance)
                            .serializerByType(Long.TYPE, ToStringSerializer.instance)
                            .serializationInclusion(JsonInclude.Include.NON_NULL)
                            //指定反序列化类型,也可以使用@JsonFormat(pattern = "yyyy-MM-dd")替代。主要是mvc接收日期时使用
                            .deserializerByType(LocalTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")))
                            .deserializerByType(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")))
                            .deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
                            // 日期序列化,主要返回数据时使用
                            .serializerByType(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")))
                            .serializerByType(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")))
                            .serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        }
    }
    

    三、总结

    在使用java.time API的过程中,除了会遇到前文所说的序列化问题之外,可能还会遇到以下问题:

    • 时区问题:LocalDateTime不包含时区信息,这可能导致在不同时区的用户之间出现不一致性。为了避免这个问题,您应该考虑使用ZonedDateTime或OffsetDateTime,并确保在处理日期和时间时考虑时区。
    • 数据库交互:当与数据库交互时,要确保数据库列的数据类型与正在使用的Java日期类型相匹配。例如,如果使用的是PostgreSQL,则可能需要使用timestamp without time zone列类型来存储日期和时间。
    • 默认值和验证:在某些情况下,可能希望为日期或时间字段设置默认值或进行验证。使用Spring的验证注解(如@NotNull或@Size)可以帮助我们确保输入的有效性。
    • 跨时区处理:由于LocalDateTime不包含时区信息,当与全球用户互动时,需要特别注意时区转换。考虑使用像Joda-Time这样的库来帮助我们处理复杂的时区转换。
    • 处理过去和未来的日期:在处理历史事件或计划未来的活动时,请确保我们的应用程序能够正确地处理这些日期。考虑使用像Period或Duration这样的类来计算日期之间的差异。

    我也会及时的更新后续实践中所遇到的问题,希望与诸位看官一起进步。

    如有错误,还望批评指正。

    标签:SpringBoot,记录,MM,dd,LocalDateTime,new,序列化,class
    From: https://www.cnblogs.com/breezefaith/p/17976014

    相关文章

    • 『数学记录』概率导论(一):样本空间与概率
        概率系列的第一篇文章。概率是用计算概括的常识。——拉普拉斯Part1 集合  在概率论中,集合论的应用是极为重要的,许多问题的处理都需要集合运算。下面首先引进集合相关的记号与术语。  将一些研究对象放在一起,形成集合,而这些对象就称为集合的元素。若\(x\)是......
    • 在springboot中controller控制器的crud语句@RequestBody遗落的报错
      在进行java练习的过程中,对一个单链表进行增删改查时发现了如下错误:对编译器的控制台进行检查之后,发现了报错语句如下:2024-01-2619:43:52.551ERROR18544---[p-nio-80-exec-5]o.a.c.c.C.[.[.[/].[dispatcherServlet]:Servlet.service()forservlet[dispatcherSe......
    • 自动化测试平台搭建背景及记录
      在目前产品的迭代过程中,公司现有的自动化测试体系存在很多问题,大多数情况是人工进行用例回归测试,低效且易出错,导致测试流程在效率和品质方面均未达到理想状态。同时,业务上线周期的日益缩短也导致产品质量的不稳定性也愈发突出,出现版本质量不统一的问题。流程下也伴随着以下痛点:测试......
    • 学习记录14
      本次学习学习了Dataframe方面的知识DataFrameDataFrame概念SparkSQL增加了DataFrame(即带有Schema信息的RDD),使用户可以在SparkSQL中执行SQL语句,数据既可以来自RDD,也可以是Hive、HDFS、Cassandra等外部数据源,还可以是JSON格式的数据SparkSQL目前支持Scala、Java、Python......
    • 学习记录15
      本次学习学习了将dataframe里吗有结构的数据加载到mysql以及进行读这里采用独立应用程序的方式读取MySQL数据库内容。创建一个代码文件SparkReadMySQL.scala,其内容如下:importorg.apache.log4j.{Level,Logger}importorg.apache.spark.sql.SparkSessionobjectSparkRea......
    • 自动化测试平台搭建背景及记录
      在目前产品的迭代过程中,公司现有的自动化测试体系存在很多问题,大多数情况是人工进行用例回归测试,低效且易出错,导致测试流程在效率和品质方面均未达到理想状态。同时,业务上线周期的日益缩短也导致产品质量的不稳定性也愈发突出,出现版本质量不统一的问题。流程下也伴随着以下痛点:......
    • 记录--h5端调用手机摄像头实现扫一扫功能
      这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助一、前言最近有遇到一个需求,在h5浏览器中实现扫码功能,其本质便是打开手机摄像头定时拍照,特此做一个记录。主要技术栈采用的是vue2,使用的开发工具是hbuilderX。经过测试发现部分浏览器并不支持打开摄像头,测试了......
    • SpringBoot中Bean的条件装配
      目录概述ProfileConditionalConditionalOnConditionalOnProperty概述众所周知,SpringBoot最腻害的地方就是容器,开发人员的日常工作就是编写bean,并由框架扫描存到容器里面,当程序跑起来的时候,各种bean协同工作完成了软件功能。那么容器是什么呢?从概念层面来讲,容器是一个池子;从物......
    • 使用Spring Data JPA实现审计功能,记录创建人、创建时间、最后修改时间和最后修改人
      目录前言近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。本项目为前后端分离开发,后端基于Java21和SpringBoot3开发,后端使用SpringSecurity、JWT、SpringDataJP......
    • SpringBoot中集成XXL-JOB分布式任务调度平台,轻量级、低侵入实现定时任务
      场景XXL-JOBhttps://www.xuxueli.com/xxl-jobXXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。特性:1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生......