MyBaits查询MySQL日期类型结果相差8个小时

发布时间 2023-07-09 14:27:02作者: nuccch

问题描述

在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