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

时间:2014-03-05 22:16:47

标签: java hibernate date datetime jpa

我使用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的toDate()方法{{1} } class获取JDK DateTime对象以遵守映射:

Date

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

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

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

它存储为

2014-03-09T02:55:00Z

我想要的是将其存储为

09-Mar-14 03:55:00

然而,在CDT,3月9日凌晨2:55不存在(“春季前进”)。所以(JDK?Hibernate?)正在向前滚动日期。

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

注意:我无法使用

设置默认TimeZone
09-Mar-14 02:55:00

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

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

3 个答案:

答案 0 :(得分:7)

我自己遇到了这个问题。我看到的是,即使您已将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类在将日期转换为存储在数据库中的毫秒值时使用UTC作为时区。您可以指定其他时区,但听起来像是您要存储的UTC。

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

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

HTH,

摩西

答案 1 :(得分:4)

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

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

写入数据库时​​会出现类似的情况。

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

<dependency>
    <groupId>com.montrosesoftware</groupId>
    <artifactId>DbAssist-4.2.21</artifactId>
    <version>1.0-RELEASE</version>
</dependency>

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

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

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

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

答案 2 :(得分:-2)

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

这样的事情:

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

希望它可以帮到你!