項目中使用了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)