LocalDateTime在项目中的使用(LocalDateTime对接前端通过时间戳互转、LocalDateTime对接数据库)
1. 博客编写背景
本文章的编写背景:由于在 jdk 8 中,date、timestamp 对象已经不推荐使用,所以在公司的新项目上,我计划将 localdatetime 使用在新项目中。
由于本人所在项目组,统一的前后端时间的交互方式为时间戳,而时间戳并不能直接被fastjson
或 jackson
直接转换,所以踩了不少的坑,个人建议有耐心的看完。
实现的效果如下:
- 前端传递时间戳
{ "localdatetime": 1584700466000 }
- 后端返回时间戳
{ "code": "0", "desc": "请求成功", "data": { "localdatetime": 1584700466000 } }
========================================================
若是感觉废话比较多,那么直接看标注了【★★★】的即可
个人写这个博客,并不想直接写结论,更多的是想给读者分享踩坑的过程
========================================================
2. localdatetime 前端交互
2.1 localdatetime 向前端写入时间戳
2.1.1 fastjson 默认的写入格式
本项目使用的是 fastjson
会写前端,我们先看下以下代码
- 回写前端的
vo
对象
@data public class localdatetimevo { private localdatetime localdatetime; }
- 测试方法
public static void main(string[] args) { localdatetimevo localdatetimevo = new localdatetimevo(); localdatetimevo.setlocaldatetime(localdatetime.now()); string json = json.tojsonstring(localdatetimevo); system.out.println(json); }
- 控制台输出
{"localdatetime":"2020-03-12t23:00:28.747"}
从上图中可以看出,服务端并不能正常的返回时间戳给前端。并不符合需求。
2.1.2 更改 fastjson 写入格式,让其回写时间戳 (★★★)
-
fastjson
提供了自定义json
转换的方法@jsonfiled
,我们添加serializeusing
,将其指定到我们自定义的序列化控制器即可。 - 自定义
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); } } }
- 使用我们自己写的
fastjson
序列化转换器
@data public class localdatetimevo { @jsonfield(serializeusing = localdatetimeserializer.class) private localdatetime localdatetime; }
- 再次执行测试方法,控制台输出
{"localdatetime":1584026032912}
可以看出,localdatetime
已经成功被转换为了时间戳,并且可以返回给前端。
2.2 接收前端传递的时间戳为 localdatetimme
2.2.1 localdatetime 默认接收的格式
不管我们传递时间戳(1584026032912),还是传递自定义格式("2020-03-13"),在服务端接受的时候,都会报错400。也就是说,传入的格式是错误的,无法被 spring 转换为 localdatetime
经过我的粗略测试,我发现,默认的接受格式为 localdatetime
特有的格式,即:2020-03-12t23:00:28.747
,除此之外都会报400。这种格式与 date
格式的唯一区别就在于,date
在日
与时
之间是用空格区分的,而 localdatetime
是用 t
来区分的。
2.2.2 更改 fastjson 反序列化方法,让其能够转换时间戳为 localdatetime(★★★)
-
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; } }
- 使用我们自己写的
fastjson
反序列化转换器
@data pubcli class localdatetimevo { @jsonfield(serializeusing = localdatetimeserializer.class, deserializeusing = localdatetimedeserializer.class) private localdatetime localdatetime; }
- 测试方法
public static void main(string[] args) { string json = "{\"localdatetime\":1584026032912}"; localdatetimevo localdatetimevo = json.parseobject(json, localdatetimevo.class); system.out.println(localdatetimevo); }
- 控制台执行结果展示
localdatetimevo(localdatetime=2020-03-12t23:13:52.912)
可以看出,时间戳成功被 fastjson
接受,并转换为了 localdatetime
。
2.2.3 【坑】更改 springboot 的 @requestbody 为 fastjson 接收(★★★)
当你看到这个小标题时,肯定会很疑惑,我们项目目前不就是使用的 fastjson
吗?
实际情况经过我测试,得出的结论是,我们在回写前端的时候,是使用 fastjson
进行转换的,但是在接受 json 的时候,是使用 spring 默认的 jackson
来接受的,所以这会导致,我们重写了 fastjson
的反序列化方法并未执行。前端传递时间戳给后端,后端报错400。
因此,我们需要更改 spring 默认提供的 jackson
为 fastjson
/** * 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()); } }
配置完成之后,后端与前端使用时间戳进行交互已完成。
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
的最大精度重置为对应的微妙即可。
以上两种方案调用localdatetime
的withnano()
方法即可
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