为什么 Java 和数据库时区会自动更改?

Why is the Java and DB time-zone automatically changed?

我在使用 java、spring、mysql 时发现了一个未知问题。

mysql 时区 = UTC。

和 tomcat 时区 = KST。喜欢这个代码

    @PostConstruct
    void postConstruct() {
        TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"));
    }

现在,让我们检查一下 Java 时区。

TimeZone aDefault = TimeZone.getDefault();
System.out.println("aDefault = " + aDefault); // 'Asia/Seoul' It is what i wanted.

LocalDateTime = LocalDateTime.now();
System.out.println("localDateTime = " + localDateTime); // It is based on KST. what i wanted

问题是我 运行 在数据库上查询的那一刻。

//使用了jpa,语义解释省略语法

// jpa
@Query("select a from A a where datetime >= : dateTime")

明确确认查询是在KST时间执行的,DB是以UTC时间保存的,但是DB中的值是根据KST导入符合条件的。

我真的不知道如何自动计算数据库时间。

例如,应用程序将数据存储在10点钟,值存储在DB中的1点钟(-9)。

我立即 运行 查询,所以我检查它是否发送到 select ~ where datetime >= 10:00:00

那么,由于是存储在DB中的1点,所以没有导入数据是正常的,但是数据是正常导入的。

我不使用 Spring Boot 或 JPA。但我可以给你一些一般性的观点。

您的问题不是很清楚,但您似乎正在尝试使用类似于 SQL 类型 TIMESTAMP WITH TIME ZONE 的数据类型的列在 table 中查找行。对于 JDBC 4.2,您应该使用 OffsetDateTime class 因为它是与 SQL 标准类型 TIMESTAMP WITH TIME ZONE.

匹配的类型
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;

在直接 JDBC 代码中,我将使用带有 ? 占位符和 SQL 的准备好的语句,如下所示:

SELECT *
FROM some_table
WHERE some_column >= ?
;

在准备好的语句中,传递 OffsetDateTime 作为 ? 的值。

myPreparedStatement.setObject( … , odt ) ;

检索。

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

您可以将 OffsetDateTime 调整为特定时区。同一时刻,时间轴上的同一点,不同的挂钟时间。

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;

关于您的示例代码……我想不出在任何情况下调用 LocalDateTime.now() 是正确的做法,因为该类型不能代表一个时刻,不是时间轴上的一个点。相反,使用 Instant.now() 表示 UTC 中的当前时刻,或 ZonedDateTime.now() 表示 JVM 当前默认时区中的当前时刻。对于 JDBC 工作,如上所示使用 OffsetDateTime

你说:

For example, the application stores data at 10 o'clock and the value is stored at 1 o'clock(-9) in the DB.

或者:

  • 您在列中使用了不正确的数据类型。要记录时刻,时间线上的特定点,您必须使用 TIMESTAMP WITH TIME ZONE,而不是 WITHOUT
    • 在 MySQL 中,这意味着您需要使用 TIMESTAMP,而不是 DATETIME
  • 您正在使用自以为是的中间件或工具,其反功能是在从数据库检索时注入时区调整。

永远不要使用 TimeZone class。 class 是多年前被现代 java.time classes 取代的遗留日期时间 classes 的一部分。具体替换为 ZoneId & ZoneOffset.

最后,我只是勉强提一下这个警告:如果你要预约未来的约会或类似的事情,这是错误的方法。如果您希望即使政客更改您所在时区的规则,一天中的时间也保持固定的约会(因此无论夏令时或其他时钟操作如何变化,下午 3 点的牙科预约都会保持在下午 3 点),那么您应该使用两个列,一个类似于SQL-标准类型TIMESTAMP WITHOUT TIME ZONE。并使用包含预期时区名称的文本类型的第二列。当需要在需要特定时刻的地方动态创建日历时,请在运行时组合这两个值以生成 ZonedDateTime 对象。那一刻只能在运行时短暂使用,永远不会存储。这个话题已经在 Stack Overflow 上被多次提及。搜索以了解更多信息。