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

时间:2021-04-10 02:28:41

I am using Hibernate (4.2) as my persistence provider, and I have a JPA entity that contains a Date field:

我使用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;
  }
  . . .
}

The column corresponding to START_DATE is defined as START_DATE TIMESTAMP (no time zone).

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

I am using Joda-Time (2.3) internally to my application to deal with the date (always in UTC), and just prior to persisting the Entity, I use the toDate() method of Joda's DateTime class to get a JDK Date object in order to obey the mapping:

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

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

When I look in the DB at the value that is stored, I notice that somewhere (JDK? Hibernate?) converts the Date value using the default Time Zone of the JVM where the code runs. In my case that is "America/Chicago".

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

The problem really manifests itself near Daylight Savings Time (DST). For example, if the time internally is

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

2014-03-09T02:55:00Z

it gets stored as

它被存储为

09-Mar-14 03:55:00

What I would like, is for it to be stored as

我想要的是将它存储为

09-Mar-14 02:55:00

However, in CDT, 2:55AM on March 9 does not exist ("Spring forward"). So something (JDK? Hibernate?) is rolling the date forward.

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

I would like for the instant that gets stored in the DB to be in UTC. After all, that's how I am dealing with it internally to my application, but as soon as I hand it off to be persisted, it gets converted to my default time zone.

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

Note: I am unable to set the default TimeZone using

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

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

because the JVM on which I'm running is shared across multiple applications.

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

How do I store the date in UTC without setting the JVM default Time Zone to UTC?

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

3 个解决方案

#1


7  

I ran into this myself. What I saw is that even though you've specified UTC as the time zone in your Date (and can see this by printing it out and seeing the 'Z' at the end), for some reason, the JVM wants to take over and convert the date for you using the JVM's default time zone.

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

Anyway, what you need is a custom mapping to work around this. Try using Jadira:

无论如何,你需要的是一个自定义映射来解决这个问题。尝试使用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;
  }
  . . .
}

By default Jadira's PersistentDate class uses UTC as the time zone when it converts the date to the millisecond value that gets stored in the DB. You can specify other time zones, but it sounds like UTC is what you want to store.

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

As the comment to your post suggests, sometimes the tool you use to query the DB is doing the mindlessly stupid automatic-what's-my-JDK-default-TZ based conversion for you, leading you to believe the value is still incorrect.

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

You may try also to store the raw value (as an INTEGER) just to convince yourself that the correct millisecond value is being stored.

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

HTH,

Mose

#2


4  

There is an article about this unexpected time zone shift issue which you can check out here. It provides the explanation for the root of the problem and shows how to deal with it. Of course, the main presumption is that we want to store dates as UTC in the database.

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

For example, whenever you read a date from the database (let's say: 9:54 UTC), JDBC skips any information about the time zone. So what JVM recieves through JDBC is is a date interpreted as if it was in the local time zone (for my case, 9:54 UTC+2). If the local time zone differs from UTC (and it usually does) we end up with the incorrect time shift.

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

The similar situation occurs when writing to the DB.

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

There is a small open source project DbAssist providing fixes for different versions of Hibernate. So if you are using Hibernate 4.2.21 just add the following Maven dependency to your POM file and your problem is solved (the detailed installation instructions of setup with e.g. Spring Boot can be found on the library github).

有一个小型开源项目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>

After applying this fix, your java.util.Date fields in the entity classes will be read and persisted as expected: as if they were stored as UTC in the database. If you are using JPA annotations, you don't have to change the mapping in the entities (as in one of the previous response); it is done automatically.

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

Internally, the fix uses a custom UTC date type, which overrides Hibernate's date type in order to force it to treat all dates in the DB as UTC. Then, to apply the mapping from java.util.Date to UtcDateType, it uses @Typedef annotation. See below:

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

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

If your project depends on Hibernate HBM files or other versions of Hibernate, go to project's github wiki for more detailed instructions how to install the proper fix.

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

#3


-2  

Have you tried to use getDateTime(DateTimeZone x) from the DateTime object?

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

Something like this:

像这样的东西:

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

Hope it helps you!

希望它能帮到你!

#1


7  

I ran into this myself. What I saw is that even though you've specified UTC as the time zone in your Date (and can see this by printing it out and seeing the 'Z' at the end), for some reason, the JVM wants to take over and convert the date for you using the JVM's default time zone.

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

Anyway, what you need is a custom mapping to work around this. Try using Jadira:

无论如何,你需要的是一个自定义映射来解决这个问题。尝试使用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;
  }
  . . .
}

By default Jadira's PersistentDate class uses UTC as the time zone when it converts the date to the millisecond value that gets stored in the DB. You can specify other time zones, but it sounds like UTC is what you want to store.

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

As the comment to your post suggests, sometimes the tool you use to query the DB is doing the mindlessly stupid automatic-what's-my-JDK-default-TZ based conversion for you, leading you to believe the value is still incorrect.

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

You may try also to store the raw value (as an INTEGER) just to convince yourself that the correct millisecond value is being stored.

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

HTH,

Mose

#2


4  

There is an article about this unexpected time zone shift issue which you can check out here. It provides the explanation for the root of the problem and shows how to deal with it. Of course, the main presumption is that we want to store dates as UTC in the database.

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

For example, whenever you read a date from the database (let's say: 9:54 UTC), JDBC skips any information about the time zone. So what JVM recieves through JDBC is is a date interpreted as if it was in the local time zone (for my case, 9:54 UTC+2). If the local time zone differs from UTC (and it usually does) we end up with the incorrect time shift.

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

The similar situation occurs when writing to the DB.

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

There is a small open source project DbAssist providing fixes for different versions of Hibernate. So if you are using Hibernate 4.2.21 just add the following Maven dependency to your POM file and your problem is solved (the detailed installation instructions of setup with e.g. Spring Boot can be found on the library github).

有一个小型开源项目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>

After applying this fix, your java.util.Date fields in the entity classes will be read and persisted as expected: as if they were stored as UTC in the database. If you are using JPA annotations, you don't have to change the mapping in the entities (as in one of the previous response); it is done automatically.

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

Internally, the fix uses a custom UTC date type, which overrides Hibernate's date type in order to force it to treat all dates in the DB as UTC. Then, to apply the mapping from java.util.Date to UtcDateType, it uses @Typedef annotation. See below:

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

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

If your project depends on Hibernate HBM files or other versions of Hibernate, go to project's github wiki for more detailed instructions how to install the proper fix.

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

#3


-2  

Have you tried to use getDateTime(DateTimeZone x) from the DateTime object?

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

Something like this:

像这样的东西:

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

Hope it helps you!

希望它能帮到你!