JDBC Mysql 时区问题
JDBC Mysql 时区问题

JDBC 链接 Mysql 提示:时区不识别

错误日志

c.m.c.e.InvalidConnectionAttributeException: The server time zone value 'PDT' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specific time zone value if you want to utilize time zone support.

原因

由于 Client 端连接字符串没有设置 serverTimezone=America/Los_Angeles 参数, 同时 Mysql 端也没有设置时区 time_zone, 此时 Mysql 采用 OS 操作系统system_time_zone, 当这个值无法被 JDBC SDK 识别时,就会提示如上错误

详细分析

Mysql 时区

SHOW variables LIKE '%time_zone%';
SELECT @@global.time_zone, @@session.time_zone;

备注

SYSTEM 代表 时区来自 system_time_zone, 而 system_time_zone 来自 OS 操作系统,启动 Mysql 时获取

@@global.time_zone 代表 Server 全局 时区

@@session.time_zone 代表 Client 当前会话 时区

如果 Mysql 没有设置 time_zone ,显示结果如下:

Variable_name Value
system_time_zone PDT
time_zone SYSTEM
@@global.time_zone @@session.time_zone
SYSTEM SYSTEM

如果 Mysql 设置了 time_zone ,显示结果如下:

Variable_name Value
system_time_zone PDT
time_zone America/Los_Angeles
@@global.time_zone @@session.time_zone
America/Los_Angeles America/Los_Angeles

JDBC SDK

SDK mysql-connector-java 版本 8.0.18

源码分析

mysql-connector-java-8.0.18.jar!\com\mysql\cj\protocol\a\NativeProtocol.class

备注

Mysql 返回 time_zone == SYSTEM 时,读取 system_time_zone

如果 Client 连接字符串 没有 配置 serverTimezone 或者 值为 Null 或者 Empty; 验证 system_time_zone 是否符合预期

如果 Client 连接字符串 配置 serverTimezone ,则验证 serverTimezone 是否符合预期

mysql_jdbc_8.0.18_Protocol alt

源码片段

public void configureTimezone() {
    String configuredTimeZoneOnServer = this.serverSession.getServerVariable("time_zone");
    if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) {
        configuredTimeZoneOnServer = this.serverSession.getServerVariable("system_time_zone");
    }

    String canonicalTimezone = (String)this.getPropertySet().getStringProperty(PropertyKey.serverTimezone).getValue();
    if (configuredTimeZoneOnServer != null && (canonicalTimezone == null || StringUtils.isEmptyOrWhitespaceOnly(canonicalTimezone))) {
        try {
            canonicalTimezone = TimeUtil.getCanonicalTimezone(configuredTimeZoneOnServer, this.getExceptionInterceptor());
        } catch (IllegalArgumentException var4) {
            throw (WrongArgumentException)ExceptionFactory.createException(WrongArgumentException.class, var4.getMessage(), this.getExceptionInterceptor());
        }
    }

    if (canonicalTimezone != null && canonicalTimezone.length() > 0) {
        this.serverSession.setServerTimeZone(TimeZone.getTimeZone(canonicalTimezone));
        if (!canonicalTimezone.equalsIgnoreCase("GMT") && this.serverSession.getServerTimeZone().getID().equals("GMT")) {
            throw (WrongArgumentException)ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("Connection.9", new Object[]{canonicalTimezone}), this.getExceptionInterceptor());
        }
    }

    this.serverSession.setDefaultTimeZone(this.serverSession.getServerTimeZone());
}

mysql-connector-java-8.0.18.jar!\com\mysql\cj\util\TimeUtil.class

备注

time_zone+08:00 或者 -07:00; 直接返回 GMT + time_zone

否则 time_zone 必须存在于 /com/mysql/cj/util/TimeZoneMapping.properties

mysql_jdbc_8.0.18_TimeUtil alt

源码片段

public static String getCanonicalTimezone(String timezoneStr, ExceptionInterceptor exceptionInterceptor) {
    if (timezoneStr == null) {
        return null;
    } else {
        timezoneStr = timezoneStr.trim();
        if (timezoneStr.length() > 2 && (timezoneStr.charAt(0) == '+' || timezoneStr.charAt(0) == '-') && Character.isDigit(timezoneStr.charAt(1))) {
            return "GMT" + timezoneStr;
        } else {
            Class var2 = TimeUtil.class;
            synchronized(TimeUtil.class) {
                if (timeZoneMappings == null) {
                    loadTimeZoneMappings(exceptionInterceptor);
                }
            }

            String canonicalTz;
            if ((canonicalTz = timeZoneMappings.getProperty(timezoneStr)) != null) {
                return canonicalTz;
            } else {
                throw (InvalidConnectionAttributeException)ExceptionFactory.createException(InvalidConnectionAttributeException.class, Messages.getString("TimeUtil.UnrecognizedTimezoneId", new Object[]{timezoneStr}), exceptionInterceptor);
            }
        }
    }
}

链接字符串

参考链接字符串参数

?serverTimezone=America/Los_Angeles&useUnicode=true&characterEncoding=utf8&useServerPrepStmts=true&autoReconnect=true&failOverReadOnly=false&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true

参考文档

如何设置 mysql 的时区

JDBC MySQL 时区问题

MySQL driver 读取时间时的时区处理


最后修改于 2024-03-12

此篇文章的评论功能已经停用。