SpringBoot中使用LocalDateTime踩坑记录

文章目录

    • 前言
    • 一、为什么推荐使用java.time包的LocalDateTime而不是java.util的Date?
    • 二、使用LocalDateTime和LocalDate时遇到了哪些坑?
      • 2.1 Redis序列化报错
        • 2.1.1 问题现象
        • 2.1.2 问题分析
        • 2.1.3 解决方案
      • 2.2 LocalDateTime和LocalDate类型的属性返回给前端的值格式不正确
        • 2.2.1 问题现象
        • 2.2.2 解决方案
    • 三、总结

前言

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

本项目基于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 exceptionorg.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.png

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 {@Beanpublic RedisSerializer<Object> redisSerializer() {ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);// 必须设置,否则无法将JSON转化为对象,会转化成Map类型objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);// 自定义ObjectMapper的时间处理模块JavaTimeModule javaTimeModule = new JavaTimeModule();javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));objectMapper.registerModule(javaTimeModule);// 禁用将日期序列化为时间戳的行为objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);//创建JSON序列化器return new Jackson2JsonRedisSerializer<>(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:sstime-zone: GMT+8default-property-inclusion: alwaysmvc:format:date-time: yyyy-MM-dd HH:mm:ssdate: dd/MM/yyyy

image.png

2.2.2 解决方案

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

@Configuration
public class JacksonConfig {@Beanpublic 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这样的类来计算日期之间的差异。

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/340854.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue3:使用解构赋值来读取对象里的键-值对(值也是对象)

一、前言 在Vue3中&#xff0c;想要读取一个对象的“键—值”对&#xff08;值也是一个对象&#xff09;&#xff0c;数据格式如下&#xff1a; {1:{courseName: 课程1, study: 951526, visit: 3785553}&#xff0c;2:{courseName: 课程2, study: 181630, visit: 380830}&…

超实用的公众号内容制作流程,小白也能轻松学会

公众号是以内容为王的平台&#xff0c;保持优质原创内容发布可以给公众号带来源源不断的流量&#xff0c;稳定输出内容&#xff0c;会获得平台的流量扶持。 很多小伙伴运营公众号都没有什么阅读量&#xff0c;一方面是公众号内容同质化越来越严重&#xff0c;另外一方面是公众…

NetSuite 收入管理模块与总账的数据一致性检查

收入管理模块是NetSuite的一个艰深功能领域&#xff0c;能够有所实践&#xff0c;知原理懂变化的实施顾问少之又少。很高兴&#xff0c;我们的财务顾问Chris在23年底经历了一次深入的NetSuite收入管理模块的实践&#xff0c;对收入管理模块与总账递延收入和收入数据的一致性检查…

2023年第三届【金铲奖】重磅发布!

出品|产业家 第三届金铲奖来了&#xff01; 在过去的一年时间里&#xff0c;我们清晰地看到&#xff0c;产业数字化的潮水更加汹涌澎湃且势不可挡&#xff0c;越来越多的企业开始寻求数字化转型&#xff0c;它们来自金融、工业、农业、医疗、能源、教育等等。 产业数字化&am…

Modern C++ std::mutex底层原理

前言 我时常有这样的疑问&#xff1a; std::mutex怎么就能保证后面的语句100%安全哪&#xff1f;CPU reordering就不会把这些语句重排到mutex前面执行&#xff1f;而且各个CPU都是有L1、L2缓存的&#xff0c;如果mutex后面要访问的的变量在这些缓存中怎么办&#xff1f; 带着…

中央处理器CPU(1)----指令周期和微程序

前言&#xff1a;由于期末复习计算机组成效率太慢所以抽时间写一下文章总结一下思路&#xff0c;理解不是很深&#xff0c;欢迎各位不吝赐教。 由于时间不是很充分&#xff0c;所以有些考点由于我们不考试&#xff0c;一笔带过了。 我这是期末复习总结&#xff0c;不是考研知识…

开源C语言库Melon:数据恢复算法

本文讲述开源C语言库Melon中的里德所罗门纠错码的使用。 关于 Melon 库&#xff0c;这是一个开源的 C 语言库&#xff0c;它具有&#xff1a;开箱即用、无第三方依赖、安装部署简单、中英文文档齐全等优势。 Github repo 简介 里德所罗门编码是一种纠错码技术&#xff0c;…

如何理解线程池中的参数设计

如何理解线程池中的参数设计 你的线程池的参数怎么配置&#xff1f;线程数量设置多少合理&#xff1f;如何确定一个线程池中的人物已经完成了为什么不建议使用java自带的Executors创建线程池线程池里面的阻塞队列设置多少合理&#xff1f; 考察&#xff1a;了解你对技术的掌握…

k8s-调度 13

调度器通过 kubernetes 的 watch 机制来发现集群中新创建且尚未被调度到 Node 上的 Pod。调度器会将发现的每一个未调度的 Pod 调度到一个合适的 Node 上来运行。 kube-scheduler 是 Kubernetes 集群的默认调度器&#xff0c;并且是集群控制面的一部分。 如果你真的希望或者有…

printk的使用与理解

文章目录 一、理清printk二、printk的使用三、printk的打印级别1、基本解释2、详细解释3、如何修改console_loglevel、default_message_loglevel、minimum_console_loglevel、default_console_loglevel的值 四、printk的输出地方五、其它 一、理清printk printk如何使用&#…

讲讲关于跨域的问题,什么是跨域?怎么办?

文章目录 什么是跨域如果非同源&#xff0c;共有三种行为受到限浏览器客户端和向服务器跨域请求的判定流程 跨域问题演示参考 以下内容为我结合他人知识进行的自我总结, 如有错误欢迎指出~ 什么是跨域 跨域就是不同的域名下的资源访问&#xff0c;会被浏览器的本地安全策略阻…

【Python学习】Python学习13-日期和时间

目录 【Python学习】Python学习13-日期和时间 前言通过time 获取时间戳时间元组获取当前时间&#xff0c;格式化时间格式化时间转换python中时间日期格式化符号获取日历Time 模块日历&#xff08;Calendar&#xff09;模块其他模块参考 文章所属专区 Python学习 前言 本章节主…