[Spring Boot] [JPA] 使用过程中的时区(TimeZone)问题

项目中使用了jpa(使用方便,但不适合稍复杂sql处理)和mybatis(使用了稍微复杂的sql)。
但是发现数据库mysql中存储的时间有问题。mybatis存储到mysql的时间没问题,但是jpa存储到mysql的时间有问题(比当前北京时间少了14个小时)。

背景

jdk版本:1.8
spring boot 版本:2.1.7.RELEASE
mysql-connector-java版本:8.0.17
hibernate-core版本:5.3.10
jdbc driver:com.mysql.cj.jdbc.Driver

定位过程

debug发现,java生成的日期没有问题。并且java生成的时区也是Asia/Shanghai没错。

logback.xml中jpa debug日志开启方法:

    <logger name="org.hibernate.SQL" level="DEBUG" />
    <logger name="org.hibernate.type" level="TRACE" />

日志定位如下:

15:19:47.398 [http-nio-8088-exec-3] TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [10] as [TIMESTAMP] - [2019-11-13 15:19:36.946]

看到日志就好定位了,最后通过定位,发现问题在com.mysql.cj.ClientPreparedQueryBindings这个类的如下代码:

@Override
    public void setTimestamp(int parameterIndex, Timestamp x, Calendar targetCalendar, int fractionalLength) {
        if (x == null) {
            setNull(parameterIndex);
        } else {

            x = (Timestamp) x.clone();

            if (!this.session.getServerSession().getCapabilities().serverSupportsFracSecs()
                    || !this.sendFractionalSeconds.getValue() && fractionalLength == 0) {
                x = TimeUtil.truncateFractionalSeconds(x);
            }

            if (fractionalLength < 0) {
                // default to 6 fractional positions
                fractionalLength = 6;
            }

            x = TimeUtil.adjustTimestampNanosPrecision(x, fractionalLength, !this.session.getServerSession().isServerTruncatesFracSecs());

            this.tsdf = TimeUtil.getSimpleDateFormat(this.tsdf, "''yyyy-MM-dd HH:mm:ss", targetCalendar,
                    targetCalendar != null ? null : this.session.getServerSession().getDefaultTimeZone());

            StringBuffer buf = new StringBuffer();
            buf.append(this.tsdf.format(x));
            if (this.session.getServerSession().getCapabilities().serverSupportsFracSecs()) {
                buf.append('.');
                buf.append(TimeUtil.formatNanos(x.getNanos(), 6));
            }
            buf.append('\'');

            setValue(parameterIndex, buf.toString(), MysqlType.TIMESTAMP);
        }
    }

debug发现this.session.getServerSession().getDefaultTimeZone()得到的是CST。然后通过这个timezone产成的tsdf对象(SimpleDateFormat),会将当前北京时间Timestamp转换成CST标准时间(这里CST时间是美国时间,所以时间差了14个小时)。

CST可以为如下4个不同的时区的缩写:

  • 美国中部时间:Central Standard Time (USA) UT-6:00
  • 澳大利亚中部时间:Central Standard Time (Australia) UT+9:30
  • 中国标准时间:China Standard Time UT+8:00
  • 古巴标准时间:Cuba Standard Time UT-4:00

解决方法

spring.jpa.properties.hibernate.jdbc.time_zone=Asia/Shanghai

其他springboot设置mysql-jdbc中timezone的几种方法: https://www.baeldung.com/mysql-jdbc-timezone-spring-boot

mysql配置

当然,为什么默认时间是CST呢?可以看下我的msyql配置:

mysql> show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | CST    |
| time_zone        | SYSTEM |
+------------------+--------+
2 rows in set (0.00 sec)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章