问题背景
在编写测试用例时,我发现使用JdbcTemplate查询出来的Timestamp类型数据与预期时间相差11小时。经过初步分析,我猜测这是服务器时区设置问题。临时解决方案是在数据库连接URL上添加serverTimeZone
参数,并设置为上海时间(Asia/Shanghai
),问题随即解决。但这种方法需要修改公司项目中所有相关的数据库连接URL,工作量较大且容易遗漏。
解决方案
更优雅的解决方案是直接设置MySQL数据库的时区。
-
查询当前MySQL时区设置
使用以下SQL命令查询MySQL数据库的时区设置:
SHOW GLOBAL VARIABLES LIKE '%time_zone%';
查询结果可能如下:
+------------------+--------+ | Variable_name | Value | +------------------+--------+ | system_time_zone | CST | | time_zone | SYSTEM | +------------------+--------+
这里
time_zone
的值为SYSTEM
,意味着MySQL使用系统的时区设置。而system_time_zone
的值为CST
,这在某些情况下会被误解释为美国芝加哥时间(Central Standard Time),而不是中国标准时间(China Standard Time)。 -
设置MySQL时区
为了解决时区问题,可以将MySQL的
time_zone
设置为'+8:00'
,即东八区。这可以通过以下SQL命令实现:SET GLOBAL time_zone = '+8:00';
或者,在MySQL配置文件(如
my.cnf
或my.ini
)中添加:[mysqld] time_zone = '+8:00'
问题分析:为什么会相差11小时呢?
为了深入理解这个问题,我们需要查看JDBC的源码。
-
JDBC获取Timestamp的源码
JDBC使用
resultSet.getTimestamp
方法获取类型为日期时间的字段的值。以下是简化后的源码:@Override public Timestamp getTimestamp(int columnIndex) throws SQLException { checkRowPos(); checkColumnBounds(columnIndex); return this.thisRow.getValue(columnIndex - 1, this.defaultTimestampValueFactory); }
其中,
this.thisRow.getValue
方法使用了defaultTimestampValueFactory
变量,这是一个类属性:private ValueFactory<Timestamp> defaultTimestampValueFactory;
在构造函数中,
defaultTimestampValueFactory
被初始化为:this.defaultTimestampValueFactory = new SqlTimestampValueFactory(pset, null, this.session.getServerSession().getDefaultTimeZone());
-
追踪时区设置
进入
getDefaultTimeZone
方法,该方法位于NativeServerSession
类中。在该类中,我们发现了setServerTimeZone
方法:public void setServerTimeZone(TimeZone serverTimeZone) { this.serverTimeZone = serverTimeZone; }
通过调试,我们发现
NativeProtocol
类的configureTimezone
方法调用了setServerTimeZone
。顾名思义,configureTimezone
方法用于配置时区。 -
configureTimezone方法的逻辑
configureTimezone
方法包含以下代码:String configuredTimeZoneOnServer = this.serverSession.getServerVariable("time_zone"); if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) { configuredTimeZoneOnServer = this.serverSession.getServerVariable("system_time_zone"); }
逻辑是:如果
time_zone
设置为SYSTEM
,则使用system_time_zone
的值。然而,如果system_time_zone
的值是CST
,在某些情况下会被解释为美国芝加哥时间,与中国时间相差13个小时(夏令时期间可能相差14小时,但数据库查询结果可能由于其他因素显示为相差11小时)。
示例代码与运行结果
以下是一个简单的Java示例,展示如何使用JDBC连接MySQL并查询时间字段:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.Timestamp;
public class JdbcTimestampExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/yourdatabase?useSSL=false&serverTimezone=Asia/Shanghai";
String user = "yourusername";
String password = "yourpassword";
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT NOW() AS current_time")) {
while (rs.next()) {
Timestamp timestamp = rs.getTimestamp("current_time");
System.out.println("Current Time from Database: " + timestamp);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果(假设数据库时区已设置为'+8:00'
):
Current Time from Database: 2023-10-01 14:30:00.0
如果数据库时区未设置正确,结果可能会显示为美国芝加哥时间,例如:
Current Time from Database: 2023-09-30 23:30:00.0
补充
本文使用的MySQL JDBC驱动版本是8.0.18
,驱动类是com.mysql.cj.jdbc.Driver
。通过设置数据库时区或连接URL中的serverTimeZone
参数,可以有效解决时区不一致的问题。希望这篇文章能帮助你更好地理解和解决JDBC查询Timestamp值时区不一致的问题。