解决 SpringBoot 应用中 MySQL 时区配置引起的时间不一致问题

在开发 SpringBoot 项目时,表中有两个时间字段

  • 一个通过 Java 代码使用 new Date() 方法获取当前时间再插入数据库
  • 另一个是使用 MySQL 的 CURRENT_TIMESTAMP 作为默认值

实际运行时发现数据库中的这两个时间值不一致,代码插入的时间比数据库自动生成的时间早了8小时,最终发现是 yml 配置问题

在此记录下我的解决过程,如有错误,欢迎指正!

1. 问题描述

开发环境:MySQL 5.7.19、Java 8、IntelliJ IDEA 2020.3

1.1 问题详情

  • Java 代码中设置用户加入时间
user.setJoinTime(new Date());
  • 数据库表的建表语句中 joinTimecreateTime 字段的定义
joinTime   datetime null
createTime datetime default CURRENT_TIMESTAMP
  • yaml 中的配置serverTimezone=UTC
spring:
    datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/thr?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
  • 运行代码后数据中插入数据后的时间值
    • joinTime:2024-07-14 08:49:19
    • createTime:2024-07-14 16:49:18

  • 现象:实际插入时间是 createTime,而 joinTime 按理来说应该与 createTime 的值一致,但从结果看出始终比实际时间早了八小时

1.2 原因分析

  • 时区对比

    • UTC (Coordinated Universal Time):协调世界时(UTC)是全球标准时间,作为时间的基准,不受地区时区影响

      • 时差:UTC+0,无时区偏移

      • 用途:广泛用于全球化的服务器和系统,以避免时区差异带来的复杂性

    • Asia/Shanghai (中国标准时间 CST):用于中国大陆地区

      • 时差:UTC+8,比UTC晚8小时

      • 用途:适用于中国本地化应用,时间存储和显示都基于北京时间,如果需要考虑1986年到1991年夏令时的历史数据,该时区会自动考虑这段时间夏令时的影响

  • 查看JVM默认时区:在Application文件中添加代码,运行程序,输出当前时间和时区信息

    • 运行结果JVM TimeZone: Asia/Shanghai
public static void main(String[] args) {
    System.out.println("JVM TimeZone: " + TimeZone.getDefault().getID());
    SpringApplication.run(Application.class, args);
}

  • 查看本地数据库时区:为东八区时间(北京时间)
SELECT @@global.time_zone, @@session.time_zone;

  • 分析:可以看出本地数据库和JVM时区其实都是东八区,而我在yml里配置的时区是 UTC,对照结果来看可知:
    • joinTime 字段使用 new Date() 在JVM默认时区(Asia/Shanghai)获取时间并插入,此时,JVM时间为北京时间 2024-07-14 16:49:19
    • 但由于数据库连接配置为UTC,实际插入时间会减去八小时,故转换为 2024-07-14 08:49:19(UTC时间)
    • createTime 字段本身就是数据库自动生成的,而我的数据库本地配置本来就是东八区时间,所以生成的时间没毛病

2. 解决

  • 将 yml 文件中的数据库连接配置修改为 serverTimezone=Asia/Shanghai 即可
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/thr?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai

3. 总结

  • serverTimezone=UTC 时,MySQL认为所有时间都是基于UTC时间,因此JVM的北京时间 2024-07-14 16:49:19 在插入时转换为 2024-07-14 08:49:19(UTC时间)
  • serverTimezone=Asia/Shanghai 时,MySQL认为所有时间都是基于北京时间,因此JVM的北京时间 2024-07-15 17:01:13 在插入时保持不变

4. 补充

  • 项目中后续又增加了 expireTime 字段,该字段是由前端传递过来然后插入数据库中的,发现了类似的问题
  • 前端原先传来的字段格式如下:
"expireTime": "2024-11-21T16:35:10.394Z"
  • 按理来说数据库中应该是:2024-11-21 16:35:10,可结果却增加了八小时,是 2024-11-22 00:35:10

  • 根据前面的经验分析:
    • "2024-11-21T16:35:10.394Z"Z 表示 UTC
    • 而我现在后端插入数据库的配置是 Asia/Shanghai,所以会将 16:35:10 增加八小时后再插入数据库
  • 解决办法:修改前端代码传过来的格式,传递符合 ISO 8601 格式的北京时间字符串
"expireTime": "2024-10-20T15:40:00+08:00"
  • 成功:

posted @ 2024-07-20 16:57  路有所思  阅读(900)  评论(0)    收藏  举报