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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22210570/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 14:20:04  来源:igfitidea点击:

Unwanted Automatic Time Zone Conversion Using Hibernate/JPA and JDK Date

javahibernatedatedatetimejpa

提问by J Steven Perry

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

我使用 Hibernate (4.2) 作为我的持久性提供程序,并且我有一个包含日期字段的 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 DateTimeclass to get a JDK Dateobject in order to obey the mapping:

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

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".

当我在数据库中查看存储的值时,我注意到某处(JDK?Hibernate?)使用代码运行的 JVM 的默认时区转换日期值。就我而言,这是“美国/芝加哥”。

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 不存在(“Spring forward”)。所以有些东西(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.

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

Note: I am unable to set the default TimeZone using

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

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 存储日期?

采纳答案by Schrute Farms

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 PersistentDateclass 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类在将日期转换为存储在数据库中的毫秒值时使用 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.

正如对您帖子的评论所暗示的那样,有时您用来查询数据库的工具正在为您进行愚蠢的基于自动-what's-my-JDK-default-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.

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

HTH,

哈,

Mose

摩西

回答by Thiago Cardoso

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!

希望对你有帮助!

回答by Smont

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.

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

The similar situation occurs when writing to the 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 文件中,您的问题就解决了(使用例如 Spring Boot 进行设置的详细安装说明可以在库github上找到)。

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

After applying this fix, your java.util.Datefields 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.Dateto UtcDateType, it uses @Typedefannotation. See below:

在内部,此修复程序使用自定义 UTC 日期类型,该类型覆盖 Hibernate 的日期类型,以强制其将数据库中的所有日期视为 UTC。然后,为了应用从java.util.Dateto的映射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 以获取更详细的说明如何安装正确的修复程序。