使用Hibernate / JPA和JDK Date进行不需要的自动时区转换

我使用Hibernate(4.2)作为我的持久性提供程序,我有一个包含Date字段的JPA实体:

@Entity @Table(name = "MY_TABLE") public class MyTable implements Serializable { . . . @Temporal(TemporalType.TIMESTAMP) @Column(name = "START_DATE") private Date startDate; public Date getStartDate() { return startDate; } public void setStartDate(Date startDate) { this.startDate = startDate; } . . . } 

与START_DATE对应的列定义为START_DATE TIMESTAMP (无时区)。

我在我的应用程序内部使用Joda-Time(2.3)处理日期(始终以UTC格式),并且在持久化实体之前,我使用Joda的DateTime类的toDate()方法来获取JDK Date对象为了遵守映射:

 public void myMethod(DateTime startDateUTC) { . . . MyTable table = /* obtain somehow */ table.setStartDate(startDateUTC.toDate()); . . . } 

当我在DB中查看存储的值时,我注意到某处(JDK?Hibernate?)使用代码运行的JVM的默认时区转换Date值。 在我的情况下是“美国/芝加哥”。

问题确实在夏令时(DST)附近显现出来。 例如,如果内部时间是

 2014-03-09T02:55:00Z 

它被存储为

 09-Mar-14 03:55:00 

我想要的是将它存储为

 09-Mar-14 02:55:00 

但是,在CDT中,3月9日凌晨2:55不存在(“春季前进”)。 所以(JDK?Hibernate?)正在向前推进日期。

我想将存储在DB中的瞬间设置为UTC。 毕竟,这就是我在我的应用程序内部处理它的方式,但是一旦我将其交给持久化,它就会转换为我的默认时区。

注意: 我无法使用默认TimeZone设置

 TimeZone.setDefault(TimeZone.getTimeZone("UTC")) 

因为我运行的JVM在多个应用程序之间共享。

如何在不将JVM默认时区设置为UTC的情况下以UTC格式存储日期?

我自己也碰到了这个。 我看到的是,即使您已将UTC指定为日期中的时区(并且可以通过打印出来并在末尾看到’Z’来看到这一点),出于某种原因,JVM希望接管并且使用JVM的默认时区为您转换日期。

无论如何,你需要的是一个自定义映射来解决这个问题。 尝试使用Jadira :

 @Entity @Table(name = "MY_TABLE") public class MyTable implements Serializable { . . . @Column(name = "START_DATE") @Type(type="org.jadira.usertype.dateandtime.legacyjdk.PersistentDate") private Date startDate; public Date getStartDate() { return startDate; } public void setStartDate(Date startDate) { this.startDate = startDate; } . . . } 

默认情况下,Jadira的PersistentDate类在将日期转换为存储在DB中的毫秒值时使用UTC作为时区。 您可以指定其他时区,但听起来像是您要存储的UTC。

正如您对post的评论所暗示的那样,有时您用来查询数据库的工具正在为您进行无意识的愚蠢自动 – 我 – 我 – JDK – 默认 – TZ转换,导致您认为该值仍然不正确。

您也可以尝试存储原始值(作为INTEGER),只是为了说服自己正在存储正确的毫秒值。

HTH,

摩西

有一篇关于这个意外时区转移问题的文章,你可以在这里查看 。 它提供了问题根源的解释,并说明了如何处理它。 当然,主要的假设是我们希望在数据库中将日期存储为UTC。

例如,每当您从数据库中读取日期时(比如说:9:54 UTC),JDBC就会跳过有关时区的任何信息。 所以JVM通过JDBC收到的是一个日期,解释为它在本地时区(对于我的情况,9:54 UTC + 2)。 如果本地时区与UTC不同(通常也是如此),我们最终会得到不正确的时移。

写入DB时会发生类似情况。

有一个小型开源项目DbAssist为不同版本的Hibernate提供修复。 因此,如果您正在使用Hibernate 4.2.21,只需将以下Maven依赖项添加到您的POM文件中,您的问题就解决了(可以在库github上找到详细的安装说明,例如Spring Boot)。

  com.montrosesoftware DbAssist-4.2.21 1.0-RELEASE  

应用此修复程序后,将按预期读取和保留实体类java.util.Date字段:就好像它们在数据库中存储为UTC一样。 如果您正在使用JPA注释,则不必更改实体中的映射(如上一个响应中所示); 它是自动完成的。

在内部,该修复程序使用自定义UTC日期类型,该日期类型会覆盖Hibernate的日期类型,以强制它将数据库中的所有日期视为UTC。 然后,要将java.util.Date的映射应用于UtcDateType ,它使用@Typedef注释。 见下文:

 @TypeDef(name = "UtcDateType", defaultForType = Date.class, typeClass = UtcDateType.class), package com.montrosesoftware.dbassist.types; 

如果您的项目依赖于Hibernate HBM文件或其他版本的Hibernate,请转到项目的github wiki以获取有关如何安装正确修复的更详细说明。

您是否尝试过使用DateTime对象中的getDateTime(DateTimeZone x)?

像这样的东西:

 public void myMethod(DateTime startDateUTC) { . . . MyTable table = /* obtain somehow */ table.setStartDate(startDateUTC.toDateTime(DateTimeZone.UTC)); . . . } 

希望它能帮到你!