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