当前位置:  开发笔记 > 编程语言 > 正文

如何使用JPA和Hibernate以UTC时区存储日期/时间和时间戳

如何解决《如何使用JPA和Hibernate以UTC时区存储日期/时间和时间戳》经验,为你挑选了7个好方法。

如何配置JPA/Hibernate将数据库中的日期/时间存储为UTC(GMT)时区?考虑这个带注释的JPA实体:

public class Event {
    @Id
    public int id;

    @Temporal(TemporalType.TIMESTAMP)
    public java.util.Date date;
}

如果日期是太平洋标准时间(PST)的2008年2月3日上午9:30,那么我希望将数据库中存储的2008年2月3日下午5:30的UTC时间.同样,当从数据库中检索日期时,我希望它被解释为UTC.所以在这种情况下530pm是在UTC时间下午5点.当它显示时,它将被格式化为太平洋标准时间上午9:30.



1> Vlad Mihalce..:

使用Hibernate 5.2,您现在可以使用以下配置属性强制使用UTC时区:


有关更多详细信息,请查看此文章.


我也写了那个:D现在,猜猜是谁在Hibernate中增加了对这个功能的支持?
如果是春季靴子https://moelholm.com/2016/11/09/spring-boot-controlling-timezones-with-hibernate/

2> mitchnull..:

据我所知,您需要将整个Java应用程序放在UTC时区(以便Hibernate以UTC格式存储日期),并且您需要在显示内容时转换为所需的任何时区(至少我们这样做)这条路).

在启动时,我们做:

TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC"));

并将所需的时区设置为DateFormat:

fmt.setTimeZone(TimeZone.getTimeZone("Europe/Budapest"))


据我所知,它将适用于所有情况,除非JVM和数据库服务器位于不同的时区.
mitchnull,您的解决方案将无法在所有情况下工作,因为Hibernate委托将日期设置为JDBC驱动程序,并且每个JDBC驱动程序以不同方式处理日期和时区.请参阅http://stackoverflow.com/questions/4123534/before-writing-a-java-date-to-an-sql-timestamp-column-does-jdbc-translate-the-da/4124620#4124620.
但是,如果我启动我的应用程序通知JVM"-Duser.timezone = + 00:00"属性,是不是表现相同?

3> 小智..:

Hibernate对Dates中的时区内容一无所知(因为没有),但它实际上是导致问题的JDBC层.ResultSet.getTimestamp并且PreparedStatement.setTimestamp两者都在他们的文档中说,默认情况下,当从/向数据库读取和写入时,它们会将日期转换为当前JVM时区.

我通过子类化在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));
    }
}

如果使用这些类型,则应该修复TimeType和DateType.缺点是您必须手动指定要使用这些类型而不是POJO中每个Date字段的默认值(并且还要打破纯JPA兼容性),除非有人知道更通用的覆盖方法.

更新: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  ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) {
        return new BasicBinder( 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  ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) {
        return new BasicExtractor( javaTypeDescriptor, this ) {
            @Override
            protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
                return javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance(UTC) ), options );
            }
        };
    }
}

现在,当应用程序启动时,如果将TimestampTypeDescriptor.INSTANCE设置为UtcTimestampTypeDescriptor的实例,则所有时间戳将被存储并视为以UTC格式,而不必更改POJO上的注释.[我还没有测试过这个]


你如何告诉Hibernate使用你的自定义`UtcTimestampType`?
"ResultSet.getTimestamp和PreparedStatement.setTimestamp都在他们的文档中说,默认情况下,当从/向数据库读取和写入时,它们会将日期转换为当前JVM时区/从当前JVM时区转换日期." 你有参考吗?对于这些方法,我在Java 6 Javadocs中没有看到任何提及.根据http://stackoverflow.com/questions/4123534/before-writing-a-java-date-to-an-sql-timestamp-column-does-jdbc-translate-the-da/4124620#4124620,这些如何方法将时区应用于给定的`Date`或`Timestamp`是依赖于JDBC驱动程序的.
为了使你的例子的3.6版本能够工作,我必须创建一个新类型,它基本上是TimeStampType的包装器,然后在字段上设置该类型.

4> Shane..:

通过Shaun Stone的提示添加一个完全基于并对于divestoclimb负债的答案.只是想详细说明,因为这是一个常见的问题,解决方案有点令人困惑.

这是使用Hibernate 4.1.4.Final,虽然我怀疑3.6之后的任何东西都可以工作.

首先,创建divestoclimb的UtcTimestampTypeDescriptor

public class UtcTimestampTypeDescriptor extends TimestampTypeDescriptor {
    public static final UtcTimestampTypeDescriptor INSTANCE = new UtcTimestampTypeDescriptor();

    private static final TimeZone UTC = TimeZone.getTimeZone("UTC");

    public  ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) {
        return new BasicBinder( 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  ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) {
        return new BasicExtractor( javaTypeDescriptor, this ) {
            @Override
            protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
                return javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance(UTC) ), options );
            }
        };
    }
}

然后创建UtcTimestampType,它在超级构造函数调用中使用UtcTimestampTypeDescriptor而不是TimestampTypeDescriptor作为SqlTypeDescriptor,否则将所有内容委托给TimestampType:

public class UtcTimestampType
        extends AbstractSingleColumnStandardBasicType
        implements VersionType, LiteralType {
    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 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);
    }
}

最后,在初始化Hibernate配置时,将UtcTimestampType注册为类型覆盖:

configuration.registerTypeOverride(new UtcTimestampType());

现在,时间戳不应该关注JVM进出数据库的时区.HTH.


很高兴看到JPA和Spring Configuration的解决方案.
关于在Hibernate中使用此方法与本机查询的说明.要使用这些重写类型,必须使用query.setParameter(int pos,Object value)设置值,而不是query.setParameter(int pos,Date value,TemporalType temporalType).如果您使用后者,那么Hibernate将使用其原始类型实现,因为它们是硬编码的.

5> codefinger..:

您会认为这个常见问题会由Hibernate处理.但它不!为了做到这一点,有一些"黑客".

我使用的是将Date作为Long存储在数据库中.所以我总是在1/1/70之后的几毫秒工作.然后,我的班级上有getter和setter,只返回/接受日期.因此API保持不变.不好的一面是我在数据库中长期存在.使用SQL我几乎只能做<,>,=比较 - 而不是花哨的日期运算符.

另一种方法是使用如下所述的自定义映射类型:http: //www.hibernate.org/100.html

我认为解决这个问题的正确方法是使用日历而不是日期.使用日历,您可以在持久化之前设置TimeZone.

注意:愚蠢的stackoverflow不会让我评论,所以这是对大卫的回应a.

如果您在芝加哥创建此对象:

new Date(0);

Hibernate将其保留为"12/31/1969 18:00:00".日期应该没有时区,所以我不确定为什么会进行调整.


我能够使用Calendar对象正确地保持时间,以便它们按照您的建议以UTC格式存储在数据库中.但是,当从数据库中读取持久化实体时,Hibernate假定它们位于本地时区且Calendar对象不正确!

6> 小智..:

这里有几个时区在运作:

    Java的Date类(util和sql),它们具有UTC的隐式时区

    JVM运行的时区,和

    数据库服务器的默认时区.

所有这些都可以是不同的.Hibernate/JPA存在严重的设计缺陷,因为用户无法轻松确保在数据库服务器中保留时区信息(这允许在JVM中重建正确的时间和日期).

如果没有(轻松)使用JPA/Hibernate存储时区的能力,那么信息就会丢失,一旦信息丢失,构建它就会变得很昂贵(如果可能的话).

我认为最好始终存储时区信息(应该是默认值),然后用户应该具有优化时区的可选功能(尽管它只影响显示,但在任何日期仍然存在隐式时区).

对不起,这篇文章没有提供解决方法(已在其他地方得到解答),但这是为什么总是存储时区信息很重要的合理化.不幸的是,许多计算机科学家和编程从业者似乎只是因为他们不理解"信息丢失"的观点以及如何使国际化这样的事情变得非常困难而反对对时区的需求 - 这一天非常重要,因为网站可以访问客户和组织中的人们在世界各地移动.


实际上,您可以标准化一个时区(通常是UTC),而不是始终存储时区,并在持久化(并在阅读时返回)将所有内容转换为此时区.这就是我们通常做的事情.但是,JDBC也不直接支持: - /.

7> 小智..:

使用Spring Boot JPA,在application.properties文件中使用以下代码,显然您可以根据自己的选择修改时区

spring.jpa.properties.hibernate.jdbc.time_zone = UTC

然后在您的Entity类文件中,

@Column
private LocalDateTime created;

推荐阅读
oDavid_仔o_880
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有