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

J S*_*rry 8 java datetime hibernate jpa 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;
  }
  . . .
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

2014-03-09T02:55:00Z
Run Code Online (Sandbox Code Playgroud)

它被存储为

09-Mar-14 03:55:00
Run Code Online (Sandbox Code Playgroud)

我想要的是将它存储为

09-Mar-14 02:55:00
Run Code Online (Sandbox Code Playgroud)

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

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

注意:我无法使用默认TimeZone

TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
Run Code Online (Sandbox Code Playgroud)

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

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

小智 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;
  }
  . . .
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

HTH,

摩西


小智 5

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

例如,每当您从数据库中读取日期时(比方说:UTC 时间 9:54),JDBC 都会跳过有关时区的任何信息。因此,JVM 通过 JDBC 接收到的日期是解释为本地时区的日期(对我而言,9:54 UTC+2)。如果本地时区与 UTC 不同(通常如此),我们最终会得到不正确的时移。

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

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

<dependency>
    <groupId>com.montrosesoftware</groupId>
    <artifactId>DbAssist-4.2.21</artifactId>
    <version>1.0-RELEASE</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

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

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

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

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