Java 如何使用 JPA 和 Hibernate 在 UTC 时区中存储日期/时间和时间戳
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/508019/
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
How to store date/time and timestamps in UTC time zone with JPA and Hibernate
提问by Steve Kuo
How can I configure JPA/Hibernate to store a date/time in the database as UTC (GMT) time zone? Consider this annotated JPA entity:
如何配置 JPA/Hibernate 将日期/时间存储在数据库中作为 UTC (GMT) 时区?考虑这个带注释的 JPA 实体:
public class Event {
@Id
public int id;
@Temporal(TemporalType.TIMESTAMP)
public java.util.Date date;
}
If the date is 2008-Feb-03 9:30am Pacific Standard Time (PST), then I want the UTC time of 2008-Feb-03 5:30pm stored in the database. Likewise, when the date is retrieved from the database, I want it interpreted as UTC. So in this case 530pm is 530pm UTC. When it's displayed it will be formatted as 9:30am PST.
如果日期是太平洋标准时间 (PST) 的 2008-Feb-03 上午 9:30,那么我希望将 2008-Feb-03 下午 5:30 的 UTC 时间存储在数据库中。同样,当从数据库中检索日期时,我希望将其解释为 UTC。所以在这种情况下 530pm 是 530pm UTC。显示时,它将被格式化为太平洋标准时间上午 9:30。
采纳答案by Vlad Mihalcea
With Hibernate 5.2, you can now force the UTC time zone using the following configuration property:
使用 Hibernate 5.2,您现在可以使用以下配置属性强制使用 UTC 时区:
<property name="hibernate.jdbc.time_zone" value="UTC"/>
For more details, check out this article.
有关更多详细信息,请查看这篇文章。
回答by mitchnull
To the best of my knowledge, you need to put your entire Java app in UTC timezone (so that Hibernate will store dates in UTC), and you'll need to convert to whatever timezone desired when you display stuff (at least we do it this way).
据我所知,您需要将整个 Java 应用程序置于 UTC 时区(以便 Hibernate 将日期存储在 UTC 中),并且您需要在显示内容时转换为所需的任何时区(至少我们这样做)这边走)。
At startup, we do:
在启动时,我们这样做:
TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC"));
And set the desired timezone to the DateFormat:
并将所需的时区设置为 DateFormat:
fmt.setTimeZone(TimeZone.getTimeZone("Europe/Budapest"))
回答by codefinger
You would think this common problem would be taken care of by Hibernate. But its not! There are a few "hacks" to get it right.
您会认为 Hibernate 会解决这个常见问题。但它不是!有一些“技巧”可以使它正确。
The one I use is to store the Date as a Long in the database. So I am always working with milliseconds after 1/1/70. I then have getters and setters on my Class that return/accept only Dates. So the API remains the same. The down side is that I have longs in the database. SO with SQL I can pretty much only do <,>,= comparisons -- not fancy date operators.
我使用的是将日期作为 Long 存储在数据库中。所以我总是在 1/1/70 之后使用毫秒。然后我在我的班级上有 getter 和 setter,它们只返回/接受日期。所以 API 保持不变。不利的一面是我在数据库中有 longs。所以使用 SQL 我几乎只能做 <,>,= 比较——而不是花哨的日期运算符。
Another approach is to user a custom mapping type as described here: http://www.hibernate.org/100.html
另一种方法是使用自定义映射类型,如下所述:http: //www.hibernate.org/100.html
I think the correct way to deal with this is to use a Calendar instead of a Date though. With the Calendar you can set the TimeZone before persisting.
我认为处理这个问题的正确方法是使用日历而不是日期。使用日历,您可以在坚持之前设置时区。
NOTE: Silly stackoverflow won't let me comment, so here is a response to david a.
注意:愚蠢的 stackoverflow 不允许我发表评论,所以这里是对 david a 的回应。
If you create this object in Chicago:
如果您在芝加哥创建此对象:
new Date(0);
Hibernate persists it as "12/31/1969 18:00:00". Dates should be devoid of timezone, so I'm not sure why the adjustment would be made.
Hibernate 将其保留为“12/31/1969 18:00:00”。日期应该没有时区,所以我不确定为什么要进行调整。
回答by codefinger
Date is not in any time zone (it is a millisecond office from a defined moment in time same for everyone), but underlying (R)DBs generally store timestamps in political format (year, month, day, hour, minute, second, ...) that is time-zone sensitive.
日期不在任何时区(它是每个人都相同的定义时刻的毫秒办公室),但底层 (R)DB 通常以格式(年、月、日、小时、分钟、秒、. ..) 是时区敏感的。
To be serious, Hibernate MUSTbe allow being told within some form of mapping that the DB date is in such-and-such timezone so that when it loads or stores it it does not assume its own...
说真的,Hibernate必须允许在某种形式的映射中被告知 DB 日期在某个时区,这样当它加载或存储它时,它不会假设它自己......
回答by Chris Pheby
Please take a look at my project on Sourceforge which has user types for standard SQL Date and Time types as well as JSR 310 and Joda Time. All of the types try to address the offsetting issue. See http://sourceforge.net/projects/usertype/
请查看我在 Sourceforge 上的项目,其中包含标准 SQL 日期和时间类型以及 JSR 310 和 Joda 时间的用户类型。所有类型都试图解决抵消问题。见http://sourceforge.net/projects/usertype/
EDIT: In response to Derek Mahar's question attached to this comment:
编辑:针对此评论所附的德里克·马哈 (Derek Mahar) 的问题:
"Chris, do your user types work with Hibernate 3 or greater? – Derek Mahar Nov 7 '10 at 12:30"
“Chris,您的用户类型是否适用于 Hibernate 3 或更高版本? – Derek Mahar 2010 年 11 月 7 日 12:30”
Yes these types support Hibernate 3.x versions including Hibernate 3.6.
是的,这些类型支持 Hibernate 3.x 版本,包括 Hibernate 3.6。
回答by divestoclimb
Hibernate is ignorant of time zone stuff in Dates (because there isn't any), but it's actually the JDBC layer that's causing problems. ResultSet.getTimestamp
and PreparedStatement.setTimestamp
both say in their docs that they transform dates to/from the current JVM timezone by default when reading and writing from/to the database.
Hibernate 不知道 Dates 中的时区内容(因为没有),但实际上是 JDBC 层导致了问题。ResultSet.getTimestamp
并且PreparedStatement.setTimestamp
都在他们的文档中说,在从/向数据库读取和写入时,默认情况下他们将日期转换为/从当前 JVM 时区转换。
I came up with a solution to this in Hibernate 3.5 by subclassing org.hibernate.type.TimestampType
that forces these JDBC methods to use UTC instead of the local time zone:
我在 Hibernate 3.5 中提出了一个解决方案,通过子类化org.hibernate.type.TimestampType
强制这些 JDBC 方法使用 UTC 而不是本地时区:
public class UtcTimestampType extends TimestampType {
private static final long serialVersionUID = 8088663383676984635L;
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
@Override
public Object get(ResultSet rs, String name) throws SQLException {
return rs.getTimestamp(name, Calendar.getInstance(UTC));
}
@Override
public void set(PreparedStatement st, Object value, int index) throws SQLException {
Timestamp ts;
if(value instanceof Timestamp) {
ts = (Timestamp) value;
} else {
ts = new Timestamp(((java.util.Date) value).getTime());
}
st.setTimestamp(index, ts, Calendar.getInstance(UTC));
}
}
The same thing should be done to fix TimeType and DateType if you use those types. The downside is you'll have to manually specify that these types are to be used instead of the defaults on every Date field in your POJOs (and also breaks pure JPA compatibility), unless someone knows of a more general override method.
如果您使用这些类型,应该做同样的事情来修复 TimeType 和 DateType。缺点是您必须手动指定要使用这些类型而不是 POJO 中每个日期字段的默认值(并且还会破坏纯 JPA 兼容性),除非有人知道更通用的覆盖方法。
UPDATE: Hibernate 3.6 has changed the types API. In 3.6, I wrote a class UtcTimestampTypeDescriptor to implement this.
更新:Hibernate 3.6 更改了类型 API。在 3.6 中,我写了一个类 UtcTimestampTypeDescriptor 来实现这一点。
public class UtcTimestampTypeDescriptor extends TimestampTypeDescriptor {
public static final UtcTimestampTypeDescriptor INSTANCE = new UtcTimestampTypeDescriptor();
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicBinder<X>( javaTypeDescriptor, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
st.setTimestamp( index, javaTypeDescriptor.unwrap( value, Timestamp.class, options ), Calendar.getInstance(UTC) );
}
};
}
public <X> ValueExtractor<X> getExtractor(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicExtractor<X>( javaTypeDescriptor, this ) {
@Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance(UTC) ), options );
}
};
}
}
Now when the app starts, if you set TimestampTypeDescriptor.INSTANCE to an instance of UtcTimestampTypeDescriptor, all timestamps will be stored and treated as being in UTC without having to change the annotations on POJOs. [I haven't tested this yet]
现在,当应用程序启动时,如果您将 TimestampTypeDescriptor.INSTANCE 设置为 UtcTimestampTypeDescriptor 的实例,则所有时间戳都将被存储并被视为采用 UTC 格式,而无需更改 POJO 上的注释。[我还没有测试过]
回答by joobik
Hibernate does not allow for specifying time zones by annotation or any other means. If you use Calendar instead of date, you can implement a workaround using HIbernate property AccessType and implementing the mapping yourself. The more advanced solution is to implement a custom UserType to map your Date or Calendar. Both solutions are explained in my blog post here: http://www.joobik.com/2010/11/mapping-dates-and-time-zones-with.html
Hibernate 不允许通过注释或任何其他方式指定时区。如果您使用日历而不是日期,则可以使用 HIbernate 属性 AccessType 并自己实现映射来实现解决方法。更高级的解决方案是实现自定义 UserType 来映射您的日期或日历。我的博客文章中解释了这两种解决方案:http: //www.joobik.com/2010/11/mapping-dates-and-time-zones-with.html
回答by Moa
There are several timezones in operation here:
这里有几个时区在运行:
- Java's Date classes (util and sql), which have implicit timezones of UTC
- The timezone your JVM is running in, and
- the default timezone of your database server.
- Java 的 Date 类(util 和 sql),它们具有 UTC 的隐式时区
- 您的 JVM 运行所在的时区,以及
- 数据库服务器的默认时区。
All of these can be different. Hibernate/JPA has a severe design deficiency in that a user cannot easily ensure that timezone information is preserved in the database server (which allows reconstruction of correct times and dates in the JVM).
所有这些都可以不同。Hibernate/JPA 有一个严重的设计缺陷,因为用户不能轻易确保时区信息保存在数据库服务器中(允许在 JVM 中重建正确的时间和日期)。
Without the ability to (easily) store timezone using JPA/Hibernate then information is lost and once information is lost it becomes expensive to construct it (if at all possible).
如果无法(轻松)使用 JPA/Hibernate 存储时区,那么信息就会丢失,一旦信息丢失,构建它就会变得昂贵(如果可能的话)。
I would argue that it is better to always store timezone information (should be the default) and users should then have the optional ability to optimize the timezone away (although it only really affects display, there is still an implicit timezone in any date).
我认为最好始终存储时区信息(应该是默认值),然后用户应该具有优化时区的可选能力(尽管它只真正影响显示,但在任何日期中仍然存在隐式时区)。
Sorry, this post doesn't provide a work-around (that's been answered elsewhere) but it is a rationalization of why always storing timezone information around is important. Unfortunately it seems many Computer Scientists and programming practitioners argue against the need for timezones simply because they don't appreciate the "loss of information" perspective and how that makes things like internationalization very difficult - which is very important these days with web sites accessible by clients and people in your organization as they move around the world.
抱歉,这篇文章没有提供解决方法(已在其他地方回答),但它合理解释了为什么总是存储时区信息很重要。不幸的是,似乎许多计算机科学家和编程从业者反对时区的必要性仅仅是因为他们不理解“信息丢失”的观点以及这如何使诸如国际化之类的事情变得非常困难 - 这在当今网站可访问的情况下非常重要您组织中的客户和人员在世界各地流动时。
回答by Shane
Adding an answer that's completelybased on and indebted to divestoclimb with a hint from Shaun Stone. Just wanted to spell it out in detail since it's a common problem and the solution is a bit confusing.
添加一个完全基于并感谢肖恩·斯通(Shaun Stone)暗示剥离攀登的答案。只是想详细说明一下,因为这是一个常见问题,解决方案有点混乱。
This is using Hibernate 4.1.4.Final, though I suspect anything after 3.6 will work.
这是使用 Hibernate 4.1.4.Final,虽然我怀疑 3.6 之后的任何东西都可以工作。
First, create divestoclimb's UtcTimestampTypeDescriptor
首先,创建divstoclimb的UtcTimestampTypeDescriptor
public class UtcTimestampTypeDescriptor extends TimestampTypeDescriptor {
public static final UtcTimestampTypeDescriptor INSTANCE = new UtcTimestampTypeDescriptor();
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicBinder<X>( javaTypeDescriptor, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
st.setTimestamp( index, javaTypeDescriptor.unwrap( value, Timestamp.class, options ), Calendar.getInstance(UTC) );
}
};
}
public <X> ValueExtractor<X> getExtractor(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicExtractor<X>( javaTypeDescriptor, this ) {
@Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance(UTC) ), options );
}
};
}
}
Then create UtcTimestampType, which uses UtcTimestampTypeDescriptor instead of TimestampTypeDescriptor as the SqlTypeDescriptor in the super constructor call but otherwise delegates everything to TimestampType:
然后创建 UtcTimestampType,它使用 UtcTimestampTypeDescriptor 而不是 TimestampTypeDescriptor 作为超级构造函数调用中的 SqlTypeDescriptor,否则将所有内容委托给 TimestampType:
public class UtcTimestampType
extends AbstractSingleColumnStandardBasicType<Date>
implements VersionType<Date>, LiteralType<Date> {
public static final UtcTimestampType INSTANCE = new UtcTimestampType();
public UtcTimestampType() {
super( UtcTimestampTypeDescriptor.INSTANCE, JdbcTimestampTypeDescriptor.INSTANCE );
}
public String getName() {
return TimestampType.INSTANCE.getName();
}
@Override
public String[] getRegistrationKeys() {
return TimestampType.INSTANCE.getRegistrationKeys();
}
public Date next(Date current, SessionImplementor session) {
return TimestampType.INSTANCE.next(current, session);
}
public Date seed(SessionImplementor session) {
return TimestampType.INSTANCE.seed(session);
}
public Comparator<Date> getComparator() {
return TimestampType.INSTANCE.getComparator();
}
public String objectToSQLString(Date value, Dialect dialect) throws Exception {
return TimestampType.INSTANCE.objectToSQLString(value, dialect);
}
public Date fromStringValue(String xml) throws HibernateException {
return TimestampType.INSTANCE.fromStringValue(xml);
}
}
Finally, when you initialize your Hibernate configuration, register UtcTimestampType as a type override:
最后,当您初始化 Hibernate 配置时,将 UtcTimestampType 注册为类型覆盖:
configuration.registerTypeOverride(new UtcTimestampType());
Now timestamps shouldn't be concerned with the JVM's time zone on their way to and from the database. HTH.
现在,时间戳不应该在进出数据库的途中与 JVM 的时区有关。哈。
回答by orim
I encountered just the same problem when I wanted to store the dates in the DB as UTC and avoid using varchar
and explicit String <-> java.util.Date
conversions, or setting my whole Java app in the UTC time zone (because this could lead to another unexpected issues, if the JVM is shared across many applications).
当我想将 DB 中的日期存储为 UTC 并避免使用varchar
和显式String <-> java.util.Date
转换,或将整个 Java 应用程序设置为 UTC 时区时,我遇到了同样的问题(因为这可能会导致另一个意外问题,如果 JVM 是在许多应用程序之间共享)。
So, there is an open source project DbAssist
, which allows you to easily fix the read/write as UTC date from the database. Since you are using JPA Annotations to map the fields in the entity, all you have to do is to include the following dependency to your Maven pom
file:
所以,有一个开源项目DbAssist
,它允许您轻松地将读取/写入从数据库中修复为 UTC 日期。由于您使用 JPA Annotations 来映射实体中的字段,您所要做的就是将以下依赖项包含到您的 Mavenpom
文件中:
<dependency>
<groupId>com.montrosesoftware</groupId>
<artifactId>DbAssist-5.2.2</artifactId>
<version>1.0-RELEASE</version>
</dependency>
Then you apply the fix (for Hibernate + Spring Boot example) by adding @EnableAutoConfiguration
annotation before the Spring application class. For other setups installation instructions and more use examples, just refer to the project's github.
然后通过@EnableAutoConfiguration
在 Spring 应用程序类之前添加注释来应用修复(对于 Hibernate + Spring Boot 示例)。其他设置安装说明和更多使用示例,请参考项目的github。
The good thing is that you don't have to modify the entities at all; you can leave their java.util.Date
fields as they are.
好处是你根本不需要修改实体;你可以离开他们的java.util.Date
领域。
5.2.2
has to correspond to the Hibernate version you are using. I am not sure, which version you are using in your project, but the full list of provided fixes is available on the wiki page of the project's github. The reason why the fix is different for various Hibernate versions is because Hibernate creators changed the API a couple of times between the releases.
5.2.2
必须与您使用的 Hibernate 版本相对应。我不确定您在项目中使用的是哪个版本,但在项目的github的 wiki 页面上提供了所提供修复程序的完整列表。不同 Hibernate 版本的修复程序不同的原因是因为 Hibernate 创建者在两次发布之间更改了 API。
Internally, the fix uses hints from divestoclimb, Shane and a few other sources in order to create a custom UtcDateType
. Then it maps the standard java.util.Date
with the custom UtcDateType
which handles all the necessary time zone handling.
The mapping of the types is achieved using @Typedef
annotation in the provided package-info.java
file.
在内部,该修复使用来自 divstoclimb、Shane 和其他一些来源的提示来创建自定义UtcDateType
. 然后它将标准java.util.Date
与UtcDateType
处理所有必要时区处理的自定义映射。类型的映射是使用@Typedef
提供的package-info.java
文件中的注释来实现的。
@TypeDef(name = "UtcDateType", defaultForType = Date.class, typeClass = UtcDateType.class),
package com.montrosesoftware.dbassist.types;
You can find an article herewhich explains why such a time shift occurs at all and what are the approaches to solve it.