关于时区问题的一些总结:从java 到 mybatis 到mysql的所有时区相关问题总结

文章框架:

原因:遇到了和时区相关的问题,但网上检索的结果并不如人意,都只是讲了其中的一部分问题,所以我来写个完整的,都是使用idea debug一步步利用控制变量法得出来的实验性结论。

内容:我会写的尽量详细,但有时候又会觉得太啰嗦了,所以在问题分析部分我给了两个版本,一份给大神看,一份给曾经的自己看。

遇到的问题(场景复现):

业务:

用户通过前端页面点击新增,创建了一条业务内容后,在业务一览展示中会有对应业务的创建时间信息,但用户发现显示的创建时间比他当时所在的时间少了十六个小时,比如他是北京时间17:00创建的,前端展示创建时间为01:00。

业务实现逻辑:

当用户点击新增业务时,前端传入用户输入的业务类数据,后端java程序使用mybatis向mysql插入业务类数据的同时,还要插入创建时间,对应语句简化为:

insert into testTable(userID,functionId,someData,createTime)
values('1231456','1255555','datafortest',now());

上图前三个字段的值是从前端传来的数据,最后一个使用了sql 函数 now()来获取当前时间。

java jdbc连接mysql的url是:

jdbc:mysql://${DATABASE_ADDRESS}/${DATABASE_NAME}?characterEncoding=utf8&serverTimezone=Asia/Shanghai

这里想展示的是serverTimezone=Asia/Shanghai 参数,所以只要留心这个参数就行了。

最后返回给前端的创建时间是 直接从java里调用select语句从mysql中查询createTime字段,没有做任何显示的时区转换,仅仅使用了这个方法将Date类型转换为前端接受的字符串类型:

Date createTime;//从数据库查询的时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.format(createTime);

问题分析(一针见血版本):

北京时间即为上海时间。
场景:在北京时间十六点创建的服务,查看时发现创建时间为零点。
1.创建服务的mapper里时用sql函数 (select now())作为当下时间插入数据库:该函数读取了mysql时区信息返回相对应的时间。而mysql的时区信息首先读取了mysql配置文件,如果配置文件没有设置,则读取系统时区。此例中:mysql时区信息为UTC,则当你在北京时间十六点执行的now(),插在数据库里会是八点(UTC)。少了八小时
2.数据从数据库传到java服务传过来的不是时间戳,而是带有时区信息的时间:Wed Dec 25 08:00:15 Asia/ShangHai 2019。这里的时区信息是由jdbc驱动的参数serverTimezone控制的。java使用jdbc链接数据库,配置jdbc url时,有个参数serverTimezone,如果参数值为Asia/ShangHai,则java程序<认为>数据库系统的时区为上海时区。
3.此例中数据库的时区设置实际上是UTC,而jdbc:serverTimezone=Asia/Shanghai,java程序vm的时区环境又为UTC。

所以在北京时间十六点执行的创建服务操作,到了数据库里保存的时间就是八点,再通过java服务查看服务创建时间就是零点了(因为java服务根据jdbc配置,认为数据库的时区是上海时间,默认会转为java vm的时区UTC,这样就再减八小时了。)。总共少了十六小时

问题分析(抽丝剥茧):

首先理解两个时区问题,一个是java程序运行所在环境的时区,一个是mysql数据库服务所在环境的时区:

  • java程序默认使用的时区是jvm的时区,jvm的时区可以手动配置,如果不配置就默认读取所在系统环境的时区。
  • mysql数据库的时区可以在mysql服务的配置文件里配置,如果不配置就默认读取所在系统环境的时区。
  • 在bash里使用date -R命令可以查看对应的时间信息(包含时区信息):
➜  ~ date -R
Mon, 30 Mar 2020 15:09:14 +0800

这里显示的是东八区时间,+0800 。

什么时候会和这两个时区打交道呢?先就上面案例差十六个小时来说。


开始分析

  1. 根据date -R,分别得到java所在环境时区为UTC(即零时区),mysql服务所在环境时区也是UTC。
  2. 当用户在北京时间16点执行新增操作,使用sql函数 (select now())作为当下时间插入数据库:该函数读取了mysql时区信息返回相对应的时间,根据1,此时为8点(UTC)。
  3. 在查询创建时间时,按道理,数据库的时间时8点(UTC),而java环境时区也是UTC,也没有做显示的时区转换,创建时间应该只会差八小时啊。

    这里开始idea debug查看,数据从数据库传到java服务传过来的不是时间戳,而是带有时区信息的时间,例如此处从数据库查询得到的时间虽然还是八点,但它是这样:Wed Dec 25 08:00:15 Asia/ShangHai 。what??为什么不是UTC了?数据库时区明明是UTC啊,仔细一想,好像在jdbc的url配置里见到过它!就是它-->serverTimezone=Asia/Shanghai,是这个参数控制了java如何认为mysql那边传来的时间信息的时区。例如,如果你是个经常全球跑的大人物,有人问你现在几点了,你肯定不能直接说八点了,你得说我在北京时间的八点,也就是东八区的八点。回归当前问题,serverTimezone就是用来告诉java程序mysql那边的时区的。java程序只信它。

  4. 所以这里java根据jdbc配置,从数据库获取到的创建时间是这样的Wed Dec 25 08:00:15 Asia/ShangHai,java程序一看你是上海时区(也就是北京时区),而我是UTC的,但你不显示转换时区时,他会默认转换为UTC,于是就变成了Wed Dec 25 00:00:15 UTC。然后转为字符串返回给前端就是零点了。

这就是一个createTime在前后端的奇妙历程~

发布了1 篇原创文章 · 获赞 0 · 访问量 13

猜你喜欢

转载自blog.csdn.net/weixin_37015554/article/details/105198428