【问题标题】:Why does the timezone specified in the JDBC connection string effect how Instants are stored in MySQL?为什么 JDBC 连接字符串中指定的时区会影响 Instant 在 MySQL 中的存储方式?
【发布时间】:2021-02-23 09:29:09
【问题描述】:

在我们的项目(Spring Boot 2.2.3、MySQL 5.7、Hibernate、Java 14)中,我们将所有与日期相关的字段作为数据类型 java.time.Instant。在我们的 MySQL 中,所有字段的类型都是 DATETIME。

当我为我的 JDBC 连接指定一个类似 jdbc:mysql://localhost/mydb?characterEncoding=UTF-8&useLegacyDatetimeCode=false&serverTimezone=Europe/Paris 的连接字符串并且我的实体中有一个 2020-07-13T00:00:00Z 的值时,数据库中的 2020-07-13T02:00:00Z 会被持久化(通过 IntelliJ/DataGrip 查看)。 当我使用 JDBC 连接再次读取它时,我使用2020-07-13T00:00:00Z 正确接收到它。

IntelliJ 表格视图中时间的显示似乎不受我设置的 serverTimeZone 的影响,所以我希望它显示存储在数据库中的纯值。

当我将我的 JDBC 连接的 connectionString 更改为 jdbc:mysql://localhost/mydb?characterEncoding=UTF-8&useLegacyDatetimeCode=false&serverTimezone=UTC 并且我的实体中的值为 2020-07-13T00:00:00Z 时,数据库中的 2020-07-13T00:00:00Z 会被持久化(通过 IntelliJ/DataGrip 查看)。 当我使用 JDBC 连接再次读取它时,我使用 2020-07-13T00:00:00Z 正确接收到它。

所以看起来我有一个 Instant,Java/MySQL 假定它是 UTC 并将其转换为连接字符串中指定的时区,因此在我的时区中添加冬季时间的一小时/夏季时间的两个小时。

我想了解的是谁执行这些时区调整以及为什么。因为我从MySQL documentation 了解到,这些适应不应该发生在类型 DATETIME 上。

MySQL 将 TIMESTAMP 值从当前时区转换为 UTC 进行存储,然后从 UTC 转换回当前时区进行检索。 (这不会发生在其他类型,例如 DATETIME。)默认情况下,每个连接的当前时区是服务器的时间。可以基于每个连接设置时区。只要时区设置保持不变,您就可以返回存储的相同值。如果您存储一个 TIMESTAMP 值,然后更改时区并检索该值,则检索到的值与您存储的值不同。

【问题讨论】:

  • 我想了解的是谁执行这些时区适应 JDBC 或 Java 本身.. 我投票给 Java。

标签: java mysql datetime jdbc


【解决方案1】:

MySQL '日期时间'stores sign, year, day, hour, minute, second, and fractional second。相当于LocalDateTime。

然而,你正在给它写 epoch millis(新时代 API 术语:java.time.Instant)。传统上,数据库中的日期时间值被认为是基于即时的(例如,epoch-millis,而不是基于人类 YMDhms),因此 java.sql.Timestamp 扩展了 java.util.Date (请注意,juDate 是一个赤裸裸的谎言。它代表epoch-millis,根本不是日期)。在 API 中也可以找到证明:除获取/设置 epoch-millis 的方法之外的所有方法在 j.u.Date 中均已弃用。

请注意,现代 JDBC 规范实际上需要支持新的 java.time 类型,例如 LocalDate,我可以确认,例如postgres 的 JDBC 驱动程序做到了这一点。

过去,JDBC API 会增加新的方法 - 例如,PreparedStatement 中会有一个 .setLocalTime(idxOfQuestionMark, localTimeInstance) 方法,ResultSet 中有一个 .getLocalTime(idxOrNameOfColumn)。但是,不再。任何新添加的类型都将这样使用:

LocalTime lt = resultSet.getObject(idxOrNameOfColumn, LocalTime.class);
preparedStatement.setObject(paramIndex, lt);

首先要尝试的是获取mysql存储数据的方式,java表示从mysql获取的数据/发送给mysql的方式,进行排队。因为如果 MySQL 数据库有一个 YMDhms 值,并且你的 java 代码可以观察到这个值的唯一方法是通过一个对象,其内部存储允许它只表示 epoch-millis,那么,你猜怎么着?某个地方的某个人正在进行基于时区的转换,因为没有它你不能从 epochmillis 到 YMDhms,反之亦然。如果您随后立即转换,您只是在引入错误的机会。

但是,它是 mysql,而 mysql 不是一个很好的数据库,所以上述方法不起作用的可能性很大(尽管 JDBC 规范或多或少需要对 LocalTime、LocalDate、LocalDateTime 和 ZonedDateTime 的支持,并且LDT 非常适合 MySQL 的 DATETIME 列实际包含的内容)。所以,如果这不起作用......

您将不得不绕着它跳舞,并接受转换的发生。这将意味着您正在观察的内容(连接时区会影响您阅读的内容)将保持不变。一种解决方案是忘记 DATETIME(毕竟,如果确实 LDT 实例无法发送到 JDBC MySQL 驱动程序或从 JDBC MySQL 驱动程序接收),那么您根本无法可靠地设置或获取此类列。重新设计您的数据库定义以使用 TIMESTAMP WITH TIMEZONE 代替(这就像 ZonedDateTime,它与 Instant (epoch-millis) 足够接近,转换应该不再是问题,尽管它仍然不是最佳的)。锁定两侧的区域,您可以可靠地从 epoch-millis 转换到该区域并返回。

或者,更好的是,找到一个更好的数据库引擎 :)

【讨论】:

    猜你喜欢
    • 2015-11-24
    • 2010-09-05
    • 2011-03-23
    • 1970-01-01
    • 2011-06-16
    • 1970-01-01
    • 2012-11-16
    • 2010-11-30
    相关资源
    最近更新 更多