当前位置: 首页 > 编程笔记 >

关于Java中的mysql时区问题详解

郎思远
2023-03-14
本文向大家介绍关于Java中的mysql时区问题详解,包括了关于Java中的mysql时区问题详解的使用技巧和注意事项,需要的朋友参考一下

前言

话说工作十多年,mysql 还真没用几年。起初是外企银行,无法直接接触到 DB;后来一直从事架构方面,也多是解决问题为主。

这次搭建海外机房,围绕时区大家做了一番讨论。不说最终的结果是什么,期间有同事认为 DB 返回的是 UTC 时间。

这里简单做个验证,顺便看下时区的问题到底是如何处理。

环境

openjdk version “1.8.0_242”
mysql-connector-java “8.0.20”
mysql “5.7” 时区 TZ=Europe/London

本地时区 GMT+8

创建个简单的库test及表user, 表结构如下:

CREATE TABLE `user` (
 `name` varchar(50) NOT NULL,
 `birth_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1

插入一条测试数据:

mysql> insert into `user`
  -> values ('Tom', time('2020-05-15 08:00:00'));
Query OK, 1 row affected (0.01 sec)

mysql> select * from user;
+------+---------------------+
| name | birth_date     |
+------+---------------------+
| Tom | 2020-05-14 08:00:00 |
+------+---------------------+
1 row in set (0.00 sec)

测试代码:

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useSSL=false", "root", "root");
Statement stmt = conn.createStatement();
stmt.execute("select * from user where name = 'Tom'");
ResultSet rs = stmt.getResultSet();
while (rs.next()) {
  Timestamp timestamp = rs.getTimestamp("birth_date");
  System.out.println(timestamp.toLocalDateTime().toString());
}

执行结果:

2020-05-14T15:00

分析

程序的执行过程同时用 wireshark 抓了包。可以看到一次查询,做了这么多次的交互(包含了会话初始化)。这里可以看到 #177 的交互返回查询的结果:Tom 2020-05-14 08:00:00,与 DB 中的数据相符。可见,返回的并不是 UTC 时间。

在 TCP 抓包结果中 #155 的查询语句:

/* mysql-connector-java-8.0.20 (Revision: afc0a13cd3c5a0bf57eaa809ee0ee6df1fd5ac9b) */
SELECT @@session.auto_increment_increment AS auto_increment_increment,
    @@character_set_client       AS character_set_client,
    @@character_set_connection     AS character_set_connection,
    @@character_set_results      AS character_set_results,
    @@character_set_server       AS character_set_server,
    @@collation_server         AS collation_server,
    @@collation_connection       AS collation_connection,
    @@init_connect           AS init_connect,
    @@interactive_timeout       AS interactive_timeout,
    @@license             AS license,
    @@lower_case_table_names      AS lower_case_table_names,
    @@max_allowed_packet        AS max_allowed_packet,
    @@net_write_timeout        AS net_write_timeout,
    @@performance_schema        AS performance_schema,
    @@query_cache_size         AS query_cache_size,
    @@query_cache_type         AS query_cache_type,
    @@sql_mode             AS sql_mode,
    @@system_time_zone         AS system_time_zone,
    @@time_zone            AS time_zone,
    @@transaction_isolation      AS transaction_isolation,
    @@wait_timeout           AS wait_timeout;

服务端返回的 time_zone 为 BST。与本地时区的转换,由 mysql 的 connector 自动完成。

进阶

时区自动转换

实现源码:

ResultSetImpl源码

this.defaultTimestampValueFactory = new SqlTimestampValueFactory(pset, null, this.session.getServerSession().getServerTimeZone());@Overridepublic Timestamp getTimestamp(int columnIndex) throws SQLException {
  checkRowPos();
  checkColumnBounds(columnIndex);  return this.thisRow.getValue(columnIndex - 1, this.defaultTimestampValueFactory);
}

如何确认服务端时区?

使用会话中的服务端时区进行服务端时区。会话初始化时会进行时区的确认,比如前面获取的到BST。确认时区的逻辑在NativeProtocol#configureTimezone()中:

public void configureTimezone() {
  #从mysql的响应获取 time_zone 和 system_time_zone 的设置
  String configuredTimeZoneOnServer = this.serverSession.getServerVariable("time_zone");

  if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) {
    configuredTimeZoneOnServer = this.serverSession.getServerVariable("system_time_zone");
  }

  #从 jdbc url 参数 serverTimezone 获取时区
  String canonicalTimezone = getPropertySet().getStringProperty(PropertyKey.serverTimezone).getValue();

  if (configuredTimeZoneOnServer != null) {
    //如果 jdbc url 中未通过 serverTimezone 指定时区。则从TimeZoneMapping.properties中获取mysql 回传的时区缩写对应的标准时区,比如此处的 BST => Europe/London
    //会出现无法映射的情况,不如 CEST 无法映射到 => Europe/Berlin,可以指定自html" target="_blank">定义的 Properties 文件进行映射
    // 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());
      }
    }
  }
  
  //如果 jdbc url 中通过 serverTimezone 指定了时区,则优先使用该时区
  if (canonicalTimezone != null && canonicalTimezone.length() > 0) {
    this.serverSession.setServerTimeZone(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.serverSession.getServerTimeZone().getID().equals("GMT")) {
      throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("Connection.9", new Object[] { canonicalTimezone }),
          getExceptionInterceptor());
    }
  }

}

关于 serverTimezone 的官方说明

Override detection/mapping of time zone. Used when time zone from server doesn't map to Java time zone

修改一下 jdbc url,通过serverTimezone指定时区为 GMT+8:jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useSSL=false

再次执行代码:

2020-05-14T08:00

总结

到此这篇关于关于Java中mysql时区问题的文章就介绍到这了,更多相关Java中mysql时区问题内容请搜索小牛知识库以前的文章或继续浏览下面的相关文章希望大家以后多多支持小牛知识库!

 类似资料:
  • 问题内容: 我有一个对象,我需要将其以格式插入MySQL的datetime字段中。 这给了我准备好的SQL语句字符串: 无论我指定的时区如何,返回的时间戳都是相同的时间戳。它忽略了我指定的带有时区的Calendar对象。怎么回事,我在做什么错? 问题答案: 时区只是查看日期(这是固定时间点)的不同方式。我在这里写了一个小例子(请注意断言): 此代码段的输出是(结果将根据执行日期/时间而有所不同):

  • 它给出了准备好的SQL语句字符串: 无论我指定的时区如何,返回的时间戳都是相同的时间戳。它忽略了我指定的带有时区的Calendar对象。这是怎么回事,我做错了什么?

  • 本文向大家介绍Django中和时区相关的安全问题详解,包括了Django中和时区相关的安全问题详解的使用技巧和注意事项,需要的朋友参考一下 在开发国际化网站的时候,难免会与时区打交道,通用CMS更是如此,毕竟其潜在用户可能是来自于全球各地的。Django在时区这个问题上下了不少功夫,但是很多资深的开发者都有可能尚未完全屡清楚Django中各种时间的实际意义和使用方法,导致写出错误的代码;作为安全研

  • 本文向大家介绍关于探究python中sys.argv时遇到的问题详解,包括了关于探究python中sys.argv时遇到的问题详解的使用技巧和注意事项,需要的朋友参考一下 自动化任务 这是一篇关于我在自己学习python自动化办公中遇到的问题的笔记。 关于文章读写 我用到的书是《python编程快速上手》,在学习第八章时遇到了理解上的困难。问题主要是在接触sys.argv和在创建bat文件时,出现

  • 场景: 如下图, 红框中的数据是后台传过来的时间, 因为是一串字符串, 在 ts 中我就没办法对这个时间做处理 (例如转成数字、获取年月日等) 对应的 ts 代码 问题简述1: 我在model中定义的 createTime 和 updateTime, 都是 number 类型, 并且我在接收 res 时候已经指定接收对象是一个 Icon 类型的数组了, 但为什么我最终 res.icons[0].c

  • 过去几天我一直在使用java,最近几天我收到了一个问题表单。当我试图在servlet中创建一个类时,包部分显示以下错误"** > 无法解析ObjectInputStreamjava.io.类型。它是从必需的. class文件间接引用的 当我试着评论出 导入javax.servlet.http.HttpServlet; **"部分的错误是去,但我不能扩展的http的servlet类。 此图像显示错误