JDBC:在 UTC 中插入时间戳

Posted

技术标签:

【中文标题】JDBC:在 UTC 中插入时间戳【英文标题】:JDBC: Insert timestamp in UTC 【发布时间】:2020-06-28 22:06:44 【问题描述】:

我的部分表定义(mysql)如下:

+-----------+----------+------+-----+---------+----------------+
| Field     | Type     | Null | Key | Default | Extra          |
+-----------+----------+------+-----+---------+----------------+
| ...       | ...      | ..   | ... | ...     | ...            |
| timestamp | datetime | YES  |     | NULL    |                |
| ...       | ...      | ..   | ... | ...     | ...            |
+-----------+----------+------+-----+---------+----------------+

我想使用 JDBC 更新时间戳字段。假设时间戳为 UTC

在我的 Java 代码中,我获取时间戳字段如下:

Timestamp datetime = new Timestamp(new Date().getTime());
        

java.util.Date 的文档指出:尽管 Date 类旨在反映协调世界时 (UTC),但它可能并不完全如此,具体取决于主机环境Java 虚拟机。

不幸的是,在我的 Windows 10 环境(java 版本“1.8.0_231”)中,Date 对象反映了我的本地时区。因此,添加到数据库的时间戳是本地时区,而不是 UTC。


Q) 如何获取 UTC 时间戳,以便将正确的值添加到数据库中?

【问题讨论】:

注意类型的描述。在 MySQL 中,datetime 是一种在没有时区信息的情况下存储值的类型,您需要知道使用哪个时区来存储/检索它,而 timestamp 将始终将值作为 UTC 存储在数据库中,您可以灵活地使用哪个时区来存储或检索值。 另见Is java.sql.Timestamp timezone specific? 【参考方案1】:

tl;博士

MySQL 中的数据类型错误,Java 中的类也错误。

写入类型为TIMESTAMP 而不是DATETIME 的列。 使用java.time.OffsetDateTime 而不是java.sql.Timestamp

示例代码 sn-p.

myPreparedStatement
.setObject( 
    … ,                       // Specify which placeholder `?` to fill-in.
    OffsetDateTime            // JDBC 4.2 and later requires a JDBC driver support exchanging `java.time.OffsetDateTime` objects with the database.
    .now( ZoneOffset.UTC )    // Capture the current moment as seen in UTC (an offset of zero hours-minutes-seconds). 
)

MySQL 中的数据类型错误

假设时间戳是UTC

您知道 MySQL 中的 DATETIME 不是 UTC 中的 吗? MySQL 中的那种类型没有时区的概念,也没有从 UTC 偏移的概念。

如果您正在跟踪时刻、时间线上的特定点,那么您在 MySQL 中的数据类型有误。您应该使用 TIMESTAMP 在 MySQL 中跟踪时刻。

错误的 Java 类

您正在使用与 Java 的最早版本捆绑在一起的糟糕日期时间类。这些类已被 Java 8 及更高版本中的现代 java.time 类所取代,如 JSR 310 中所定义。切勿使用 java.sql.Timestamp 类。

要获取 UTC 中的当前时刻,请使用 Instant

Instant instant = Instant.now() ;

将您在 MySQL 中的列定义为类型TIMESTAMP

可能能够通过您的 JDBC 驱动程序编写 Instant。然而,JDBC 4.2 需要对OffsetDateTime 的支持,但奇怪的是忽略了对Instant 的必要支持。没关系,我们可以轻松转换。

OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;

使用符合 JDBC 4.2 或更高版本的 JDBC 驱动程序,通过准备好的语句将其写入数据库。

myPreparedStatement.setObject( … , odt ) ;

检索。

OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;

时区问题消除

如果您按照此处看到的代码进行操作,则不会出现时区问题。

这些代码都不依赖于 JVM 当前的默认时区。 此代码均不依赖于数据库服务器当前的默认时区。


关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。 Hibernate 5 & JPA 2.2 支持 java.time

从哪里获取 java.time 类?

Java SE 8Java SE 9Java SE 10Java SE 11 和更高版本 - 包含捆绑实现的标准 Java API 的一部分。 Java 9 添加了一些小功能和修复。 Java SE 6Java SE 7 大部分 java.time 功能在ThreeTen-Backport 中向后移植到 Java 6 和 7。 Android java.time 类的 android 捆绑包实现的更高版本。 对于早期的 Android (ThreeTenABP 项目适应 ThreeTen-Backport(如上所述)。见How to use ThreeTenABP…

【讨论】:

JDBC 仅在 TIMESTAMP WITH TIME ZONE 列上定义了对 java.time.OffsetDateTime 的支持。您是否验证过getObject(.., OffsetDateTime.class) 在这种情况下是否有效? @MarkRotteveel 正如我在答案中所说的三遍,问题中的数据库在 MySQL 中使用了错误的数据类型。【参考方案2】:

Q) 如何获取 UTC 时间戳

按如下方式进行:

ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Etc/UTC"));
Timestamp timestamp = Timestamp.valueOf(zdt.toLocalDateTime());

注意事项:

    由于您想使用时间戳,我建议您将字段类型更改为TIMESTAMP。更多详情请查看this。 停止使用过时的 java.util 日期时间 API 并切换到 modern date-time API。

【讨论】:

OffsetDateTime 在这里比ZonedDateTime 更合适。并且OffsetDateTime可以直接发送到数据库中,所以不需要麻烦的遗留类java.sql.Timestamp Instant.now 没有办法将其放入 SQLQuery 中,即使在 utc 中获得时间后也节省了我的一天【参考方案3】:

您可以让 MySQL 使用此列定义插入时间戳

CREATE TABLE SOME_TABLE_NAME (
     SOME_COLUMN_NAME TIMESTAMP default CURRENT_TIMESTAMP
);

【讨论】:

以上是关于JDBC:在 UTC 中插入时间戳的主要内容,如果未能解决你的问题,请参考以下文章

将 UTC 时间戳插入 MySQL - 夏令时时区

插入时间戳时的 H2 org.h2.jdbc.JdbcSQLSyntaxErrorException

如何使用 clojure.java.jdbc 插入包含时间戳值的行?

如何使用 Spring JDBC Framework 将“null”时间戳插入 DB2 数据库

在 Lua 中获取 UTC UNIX 时间戳

给定 UTC 时间戳和 UTC 偏移量,是不是可以在 Python 中获取时区?