使用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)); . . . }
希望它能帮到你!