问题描述
在Java项目中使用MyBatis作为ORM框架,但是查询出的MySQL日期类型字段值总是比数据库表里的值多8个小时。
具体说明:
MySQL数据库表字段类型为timestamp
,映射的Java日期类型为java.util.Date
,当数据库表里的字段值为2023-07-08 00:08:38
时,查询出的Java字段值为2023-07-08 08:08:38
。显然,查询结果的时间比表里实际存储的时间值大了8个小时。
原因分析
一开始以为是映射的Java日期类型不正确,修改为java.sql.Date
依然不解决问题。
后来经过查询得知,造成查询结果与表值不一致的原因是:JDBC连接URL中设置的serverTimezone
参数不正确导致。
错误的设置:jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC
。
正确的设置:jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Asia/Shanghai
。
那么,在这里首先先解决的问题就是JDBC连接参数serverTimezone
的作用是什么。
答案:serverTimezone
参数用于设置对日期字段进行处理的时区,如果设定serverTimezone=UTC
,会比中国时间早8个小时,如果在中国,可以选择Asia/Shanghai
或者Asia/Hongkong
。
追溯JDBC源码可以发现,在com.mysql.cj.mysqla.MysqlaSession
类中有一个方法configureTimezone
,专门用于处理时区的值。
// mysql-connector-java-6.0.6 源码解读
// 对应Maven依赖配置:
// <dependency>
// <groupId>mysql</groupId>
// <artifactId>mysql-connector-java</artifactId>
// <version>6.0.6</version>
// </dependency>
public void configureTimezone() {
// 从MySQL服务端读取时区变量配置,在MySQL上可以执行`show variables like '%time_zone%'`查询
String configuredTimeZoneOnServer = getServerVariable("time_zone");
if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) {
configuredTimeZoneOnServer = getServerVariable("system_time_zone");
}
// 从JDBC连接参数`serverTimezone`中读取时区配置
String canonicalTimezone = getPropertySet().getStringReadableProperty(PropertyDefinitions.PNAME_serverTimezone).getValue();
if (configuredTimeZoneOnServer != null) {
// user can override this with driver properties, so don't detect if that's the case
if (canonicalTimezone == null || StringUtils.isEmptyOrWhitespaceOnly(canonicalTimezone)) {
try {
canonicalTimezone = TimeUtil.getCanonicalTimezone(configuredTimeZoneOnServer, getExceptionInterceptor());
} catch (IllegalArgumentException iae) {
throw ExceptionFactory.createException(WrongArgumentException.class, iae.getMessage(), getExceptionInterceptor());
}
}
}
if (canonicalTimezone != null && canonicalTimezone.length() > 0) {
// 如果JDBC连接参数serverTimezone明确配置了值,则使用该参数值作为时区设置
this.serverTimezoneTZ = TimeZone.getTimeZone(canonicalTimezone);
//
// The Calendar class has the behavior of mapping unknown timezones to 'GMT' instead of throwing an exception, so we must check for this...
//
if (!canonicalTimezone.equalsIgnoreCase("GMT") && this.serverTimezoneTZ.getID().equals("GMT")) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("Connection.9", new Object[] { canonicalTimezone }),
getExceptionInterceptor());
}
}
this.defaultTimeZone = this.serverTimezoneTZ;
}
【参考】 关于mysql的时区(下):如何设置mysql的时区 Mybatis查询Mysql datetime类型时,相差8小时 解决方案 MyBatis 处理 MySQL 时间类型 date 、datetime、timestamp