[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)

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