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 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.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 {
    @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);

        // 自定义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:ss
    time-zone: GMT+8
    default-property-inclusion: always
  mvc:
    format:
      date-time: yyyy-MM-dd HH:mm:ss
      date: dd/MM/yyyy

image.png

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这样的类来计算日期之间的差异。

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spark SQL进阶

DataFrame详解 清洗相关API 去重API 删除空缺值的API 替换缺失值的API from pyspark import SparkConf, SparkContext import os from pyspark.sql import SparkSession# 绑定指定的Python解释器 os.environ[SPARK_HOME] /export/server/spark os.environ[PYSPARK_PYTHON]…

函数——自制函数(c++)

今天进入自制函数。 自制函数&#xff0c;需要自己定义其功能。比如&#xff0c;设置一个没有参数没有返回值的积木&#xff0c;叫“aaa”。那么&#xff0c;如果想要运行“aaa”&#xff0c;就需要以下代码&#xff1a; void aaa(); 告诉系统有“aaa”…

强化学习的数学原理学习笔记 - Actor-Critic

文章目录 概览&#xff1a;RL方法分类Actor-CriticBasic actor-critic / QAC&#x1f7e6;A2C (Advantage actor-critic)Off-policy AC&#x1f7e1;重要性采样&#xff08;Importance Sampling&#xff09;Off-policy PGOff-policy AC &#x1f7e6;DPG (Deterministic AC) 本…

【自控实验】1. 线性系统串联超前校正实验

本科课程实验报告&#xff0c;有太多公式和图片了&#xff0c;干脆直接转成图片了 仅分享和记录&#xff0c;不保证全对 串联超前校正实验&#xff1a;频域设计计算(校正装置)&#xff0c;时域观察验证(校正结果) 使用matlab中的simulink进行仿真

String有没有最大长度限制?

大家都用过String字符串&#xff0c;有的人可能还不知道它的长度在某些方面是有一些限制。 public String(byte bytes[], int offset, int length);这是java.lang.String中的一个构造函数&#xff0c;可以看到它的长度是int类型&#xff0c;int的最大取值是2^31-1.但是我们却不…

python股票分析挖掘预测技术指标知识跳空缺口指标详解(5)

本人股市多年的老韭菜&#xff0c;各种股票分析书籍&#xff0c;技术指标书籍阅历无数&#xff0c;萌发想法&#xff0c;何不自己开发个股票预测分析软件&#xff0c;选择python因为够强大&#xff0c;它提供了很多高效便捷的数据分析工具包。 我们已经初步的接触与学习其中数…

Next.js 学习笔记(五)——渲染

渲染 渲染将你编写的代码转换到用户界面。React 和 Next.js 允许你创建混合 web 应用程序&#xff0c;其中部分代码可以在服务器或客户端上呈现。本节将帮助你了解这些渲染环境、策略和运行时之间的差异。 基本知识 首先&#xff0c;下列对熟悉三个基本的网络概念是有帮助的…

哈希-力扣454.四数相加Ⅱ

题目 给你四个整数数组 nums1、nums2、nums3 和 nums4 &#xff0c;数组长度都是 n &#xff0c;请你计算有多少个元组 (i, j, k, l) 能满足&#xff1a; 0 < i, j, k, l < nnums1[i] nums2[j] nums3[k] nums4[l] 0 示例 1&#xff1a; 输入&#xff1a;nums1 [1…

UI功能6大流程、接口测试8大流程这些你真的全会了吗?

在讲接口流程测试之前&#xff0c;首先需要给大家申明下&#xff1a;接口测试对于测试人员而言&#xff0c;非常非常重要&#xff0c;懂功能测试接口测试&#xff0c;就能在企业中拿到一份非常不错的薪资。 这么重要的接口测试&#xff0c;一般也是面试笔试必问。为方便大家更…

Java并发之互斥一:管程

1、简单聊聊什么是管程模型 &#xff08;共享资源&#xff09;&#xff1a;定义一个共享变量&#xff0c;可以理解锁&#xff0c;令牌这类的东西&#xff08;互斥访问共享资源&#xff09;&#xff1a;获取这个锁、令牌的时候是排好队的&#xff0c;只允许单线程访问&#xff…

LeetCode刷题---最小栈

解题思路&#xff1a; 该题通过辅助栈的方式来解决 定义数据栈(用于实现正常操作流程)和辅助栈(用于获取最小元素)。 push:首先将数据push进数据栈中&#xff0c;此时再判断辅助栈是否为空或者当前数据是否小于辅助栈中的最小元素(栈顶元素)&#xff0c;如果条件成立&#xff0…

什么是广告联盟?国内哪些广告联盟?广告联盟如何赚取收益?

开发者想要对接广告联盟获得广告变现收益&#xff0c;就要了解广告联盟的优势&#xff0c;以及广告联盟是如何获取收益的。 一、什么是广告联盟&#xff1f; 广告联盟是一种在线广告服务模式&#xff0c;将广告主和流量主联系在一起。通过广告联盟平台的技术服务&#xff0c;…

用友U8流程审批效率-SQLServer+SSRS

文章目录 @[TOC]1、 需求及效果1.1 需求1.2 效果2、 思路及SQL语句3、实现折叠明细表4、结语1、 需求及效果 1.1 需求 想要查看U8的审批流程,查看流程在哪个节点或人停留的时间,这个单据整个流程走下来需要的时间。可以更加直观方便的查看审批效率 1.2 效果 采用了SSRS上…

NLP论文阅读记录 - wos | 01 使用深度学习对资源匮乏的语言进行抽象文本摘要

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试1.3本文贡献 二.相关工作三.本文方法四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4评估指标4.5 实验结果4.6 细粒度分析 五 总结思考 前言 Abstractive text summarization of lowresourced languages usi…

企业级进销存管理系统

框架&#xff1a; 进销存管理系统&#xff0c;采用SpringBootShiroMyBatisEasyUI 项目采用Maven构建&#xff0c;数据库文件存放在 sql/jxc.sql 截图 运行项目部分截图&#xff0c; 登录界面&#xff0c;用户名admin&#xff0c;密码admin123 当前库存查询&#xff0c; 进…

C/C++ 堆排序

个人主页&#xff1a;仍有未知等待探索-CSDN博客 专题分栏&#xff1a;数据结构_仍有未知等待探索的博客-CSDN博客 欢迎大家来指教&#xff01; 一、前言 今天要介绍的是堆排序。 首先什么是堆&#xff1f;简而言之&#xff0c;堆就是二叉树的数组形式&#xff0c;用数组来存…

c++最值查找

目录 min和max函数 min_element和max_element 例 nth_element函数 例 例题 题目描述 输入描述 输出描述 解 min和max函数 只能传入两个值或一个列表 时间复杂度为O(1),数组O(n)&#xff0c;n为元素个数 min_element和max_element min_element(st,ed)返回地址[st,…

企业泛域名SSL证书

SSL数字证书是一种电子证书&#xff0c;它由CA认证机构颁发&#xff0c;提供了加密连接和身份验证的功能。SSL数字证书广泛应用于各种在线服务&#xff0c;如网页浏览、电子邮件、在线银行等。它能够提供安全的连接&#xff0c;保护用户的个人信息和交易数据不被窃取或篡改。企…

如果你还忍受校园网,那么一定要看它!随身WiFi哪个品牌最靠谱 ?高人气随身WiFi第一名

很多学生党反映校园网价格太贵&#xff0c;贵就算了&#xff0c;还非常不好用&#xff0c;真的很泪崩。 对于学生党来说&#xff0c;一个性价比极高的随身WiFi才是正正好。 但是很多学生党对随身WiFi并不了解&#xff0c;那么下面我就回答几个学生党最关心的问题。一、价格合…