为啥 Java 和 DB 时区会自动更改?

Posted

技术标签:

【中文标题】为啥 Java 和 DB 时区会自动更改?【英文标题】:Why is the Java and DB time-zone automatically changed?为什么 Java 和 DB 时区会自动更改? 【发布时间】:2021-08-15 08:49:35 【问题描述】:

我在使用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的条件。

我真的不知道如何自动计算 DB 时间。

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

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

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

【问题讨论】:

datetime 列的类型是什么?是带时区还是不带时区? 【参考方案1】:

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

您的问题并不十分清楚,但您似乎正在尝试使用类似于 SQL 类型TIMESTAMP WITH TIME ZONE 的数据类型的列在表中查找行。对于 JDBC 4.2,您应该使用 OffsetDateTime 类,因为它是与 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,如上所示。

你说:

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

要么:

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

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

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

【讨论】:

以上是关于为啥 Java 和 DB 时区会自动更改?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Visual Studio 会自动更改我的表单布局?

为啥更改 UIImageView 内容模式会影响其在 Xcode6 中的自动布局?

为啥 jumpForce 在检查器中自动更改?

为啥我在AppGallery Connect中为其他应用更改货币时,我的应用中的应用内产品价格的货币会自动更改?

核心数据-后台线程中的更新实体会自动更改主线程中的 NSManagedObject 而无需合并-为啥?

为啥表格视图单元格中的自动布局会导致表格的内容大小被错误地更改?