实战解决JDBC查询Timestamp值比中国时区慢了11或13小时的问题

问题背景

在编写测试用例时,我发现使用JdbcTemplate查询出来的Timestamp类型数据与预期时间相差11小时。经过初步分析,我猜测这是服务器时区设置问题。临时解决方案是在数据库连接URL上添加serverTimeZone参数,并设置为上海时间(Asia/Shanghai),问题随即解决。但这种方法需要修改公司项目中所有相关的数据库连接URL,工作量较大且容易遗漏。

解决方案

更优雅的解决方案是直接设置MySQL数据库的时区。

  1. 查询当前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)。

  2. 设置MySQL时区

    为了解决时区问题,可以将MySQL的time_zone设置为'+8:00',即东八区。这可以通过以下SQL命令实现:

    SET GLOBAL time_zone = '+8:00';
    

    或者,在MySQL配置文件(如my.cnfmy.ini)中添加:

    [mysqld]
    time_zone = '+8:00'
    
问题分析:为什么会相差11小时呢?

为了深入理解这个问题,我们需要查看JDBC的源码。

  1. 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());
    
  2. 追踪时区设置

    进入getDefaultTimeZone方法,该方法位于NativeServerSession类中。在该类中,我们发现了setServerTimeZone方法:

    public void setServerTimeZone(TimeZone serverTimeZone) {
        this.serverTimeZone = serverTimeZone;
    }
    

    通过调试,我们发现NativeProtocol类的configureTimezone方法调用了setServerTimeZone。顾名思义,configureTimezone方法用于配置时区。

  3. 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值时区不一致的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值