我遇到了一些我无法理解的 JPA 行为。
感谢您的任何想法
当您配置数据库不会存储该日期时间的任何时区时,您可以认为它只是将其存储为本地日期时间。由客户端应用程序(即您的情况下的 JDBC 驱动程序)在获取时区来定义其时区。
因此,如果您将 JDBC 驱动程序配置为在存储日期时间和获取相同日期时间时具有不同的时区,则会发生这种时移行为。
JDBC驱动程序的时区基本上取决于以下优先级。如果您没有明确配置高优先级项目的时区,它将默认为较低优先级项目。
Hibernate 甚至提供了一种通过属性来配置时区的方法,其优先级比 JDBC 连接 url 还要高
hibernate.jdbc.time_zone
(详细信息请参阅this)
所以我相信您的情况可以通过以下方式解释。即使您存储
2023-01-01T12:00:00 UTC
:
2023-01-01 12:00:00
UTC+1
,因此您的 Java 应用程序在从数据库获取它时会将其解释为 2023-01-01 12:00:00 UTC+1
。Instant
是基于 UTC 时区表示的,因此您将其视为 2023-01-01 11:00:00 UTC
(注:2023-01-01 12:00:00 UTC+1 = 2023-01-01 11:00:00 UTC
)因此,如果您使用 hibernate ,我建议将
hibernate.jdbc.time_zone
配置为 UTC
,它与数据库中存储的日期时间的预期时区一致。它使您的应用程序具有更具确定性的时区行为,而不会受到 JVM 或服务器操作系统的时区设置的影响。
顺便说一句,更多的是 JDBC 驱动程序调整时间而不是 JPA。即使您不使用 JPA,您也应该得到相同的行为。
我正在保存特定的日期时间。 2023-01-01T12:00:00Z(这是即时)到没有时区的 Postgresql 时间戳。
您为列选择了错误的数据类型。类型
TIMESTAMP WITHOUT TIME ZONE
不能表示时刻、时间轴上的特定点。该类型仅存储带有日期时间的日期,例如明年 1 月 23 日中午。但我们无法知道这是否是东京的中午、图卢兹的中午,还是托莱多的中午——三个截然不同的时刻,相隔几个小时。
输入字符串末尾的
Z
表示与 UTC 的零时-分-秒偏移。与日期和时间相结合,确定了一个时刻。
要存储该时刻,您需要将列定义为
TIMESTAMP WITH TIME ZONE
。
Stack Overflow 上已经对此进行了多次讨论,有许多现有的答案,包括我写的一些答案。搜索以了解更多信息,例如将 Java 类型
LocalDateTime
与一种列类型结合使用,将 OffsetDateTime
与另一种列类型结合使用。
如果您使用正确的类型,所有问题都会消失。并且存储在数据库中的数据将是有意义的而不是含糊不清的。
写作:
OffsetDateTime odt = myInstant.atOffset( ZoneOffset.UTC ) ;
myPreparedStatement.setObject( … , odt ) ;
阅读:
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ; // Accessing a column of type TIMESTAMP WITH TIME ZONE.
Instant instant = odt.toInstant() ;
了解 Postgres 存储 all
TIMESTAMP WITH TIME ZONE
值,与 UTC 的偏移量为零时-分-秒。随输入提交的任何区域或偏移信息都用于将该输入调整为 UTC。
因此,从
TIMESTAMP WITH TIME ZONE
列检索的值“始终”采用 UTC。然而,中间件、驱动程序和工具可能(不幸的是)选择对检索到的值应用区域或偏移量,从而撒谎。