JPAとHibernateを使用して日付/時刻とタイムスタンプをUTCタイムゾーンに保存する方法


106

JPA / Hibernateを構成して、日付/時刻をデータベースにUTC(GMT)タイムゾーンとして保存するにはどうすればよいですか?この注釈付きのJPAエンティティを考えてみます。

public class Event {
    @Id
    public int id;

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

日付が2008-Feb-03 9:30 am太平洋標準時(PST)の場合、2008-Feb-03 5:30 pmのUTC時刻をデータベースに保存します。同様に、データベースから日付を取得するときは、UTCとして解釈してほしい。したがって、この場合、530pmは530pm UTCです。表示されると、午前9時30分PSTとしてフォーマットされます。


1
Vlad Mihalceaの回答は更新された回答を提供します(Hibernate 5.2以降の場合)
Dinei

回答:


73

Hibernate 5.2では、次の構成プロパティを使用してUTCタイムゾーンを強制できます。

<property name="hibernate.jdbc.time_zone" value="UTC"/>

詳しくは、こちらの記事をご覧ください。


19
私もそれを書きました:Dでは、Hibernateでこの機能のサポートを追加したのは誰でしょうか?
Vlad Mihalcea

ああ、たった今、名前と写真があなたのプロフィールとそれらの記事で同じであることに気づきました...良い仕事Vlad :)
15:09にDinei

@VladMihalcea Mysqlの場合useTimezone=trueは、接続文字列でタイムゾーンを使用するようにMySqlに指示する必要があります。次に、プロパティの設定のみhibernate.jdbc.time_zoneが機能します
TheCoder

実際には、useLegacyDatetimeCodefalse に設定する必要があります
Vlad Mihalcea

2
hibernate.jdbc.time_zoneが無視されているように見えるか、PostgreSQLで使用すると効果がない
Alex R

48

私の知る限りでは、Javaアプリ全体をUTCタイムゾーンに置く必要があります(Hibernateが日付をUTCに格納するため)。そして、ものを表示するときに、希望する任意のタイムゾーンに変換する必要があります(少なくともそれを行います)こちらです)。

起動時に、次のことを行います。

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

そして、目的のタイムゾーンをDateFormatに設定します。

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

5
HitchnateがJDBCドライバーに日付の設定を委任し、各JDBCドライバーが日付とタイムゾーンを異なる方法で処理するため、ソリューションがすべてのケースで機能するわけではありません。stackoverflow.com/questions/4123534/…を参照してください。
Derek Mahar、

2
しかし、JVMの「-Duser.timezone = + 00:00」プロパティに通知してアプリを起動した場合、同じように動作しませんか?
rafa.ferreira '19年

8
私の知る限り、JVMとデータベースサーバーが異なるタイムゾーンにある場合を除いて、すべてのケースで機能します。
シェーン

stevekuoとdivestoclimbはるかに優れているの下にソリューションを参照@mitchnull 副作用証拠stackoverflow.com/a/3430957/233906
Cerber

HibernateJPAが "@Factory"および "@Externalizer"アノテーションをサポートしているかどうか、それがOpenJPAライブラリでの日時utcの処理方法です。stackoverflow.com/questions/10819862/...
Whome

44

Hibernateは(存在しないため)日付のタイムゾーン情報を認識しませんが、問題を引き起こしているのは実際には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のすべての日付フィールドのデフォルトの代わりにこれらのタイプを使用することを手動で指定する必要があることです(また、純粋な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 <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 );
            }
        };
    }
}

アプリが起動したときに、TimestampTypeDescriptor.INSTANCEをUtcTimestampTypeDescriptorのインスタンスに設定すると、すべてのタイムスタンプが格納され、POJOのアノテーションを変更することなくUTCとして扱われます。[これはまだテストしていません]


3
Hibernateにカスタムを使用するように指示するにはどうすればよいですUtcTimestampTypeか?
Derek Mahar、2010年

divestoclimb、HibernateのどのバージョンとUtcTimestampType互換性がありますか?
Derek Mahar、

2
「ResultSet.getTimestampとPreparedStatement.setTimestampの両方のドキュメントでは、データベースとの間で読み書きするときに、デフォルトで現在のJVMタイムゾーンとの間で日付を変換すると述べています。」リファレンスはありますか?これらのメソッドについては、Java 6のJavadocでこれについての言及はありません。stackoverflow.com/questions/4123534/…によると、これらのメソッドがタイムゾーンを特定の方法にどのように適用するか、DateまたはTimestampJDBCドライバーに依存するかです。
Derek Mahar、

1
昨年、JVMタイムゾーンの使用についてそれを読んだと誓ったかもしれませんが、今は見つかりません。特定のJDBCドライバーのドキュメントでそれを見つけて一般化したかもしれません。
2011

2
サンプルの3.6バージョンを機能させるには、基本的にはTimeStampTypeのラッパーである新しいタイプを作成し、そのタイプをフィールドに設定する必要がありました。
Shaun Stone、

17

Spring Boot JPAを使用して、application.propertiesファイルで以下のコードを使用します。明らかに、選択したタイムゾーンを変更できます。

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

次に、エンティティクラスファイルで、

@Column
private LocalDateTime created;

11

Shaun Stoneのヒントに基づいて、完全にダイベストクライムに基づいており、ダイベストクライミングに回答した回答を追加します。それは一般的な問題であり、解決策は少し混乱するので、それを詳細に説明したかっただけです。

これはHibernate 4.1.4.Finalを使用していますが、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 );
            }
        };
    }
}

次に、スーパーコンストラクターの呼び出しでSqlTypeDescriptorとしてTimestampTypeDescriptorではなくUtcTimestampTypeDescriptorを使用するUtcTimestampTypeを作成しますが、それ以外の場合はすべてを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);
    }
}

最後に、Hibernate構成を初期化するときに、UtcTimestampTypeを型オーバーライドとして登録します。

configuration.registerTypeOverride(new UtcTimestampType());

これで、タイムスタンプは、データベースに出入りする途中のJVMのタイムゾーンに関係するべきではありません。HTH。


6
JPAとSpring構成のソリューションを見ていただければ幸いです。
Aubergine 2013年

2
Hibernate内のネイティブクエリでこのアプローチを使用する場合の注意事項。これらのオーバーライドされたタイプを使用するには、query.setParameter(int pos、Date value、TemporalTypetemporalType)ではなく、query.setParameter(int pos、Object value)で値を設定する必要があります。後者を使用する場合、Hibernateはハードコーディングされているため、元の型の実装を使用します。
Nigel

ステートメントconfiguration.registerTypeOverride(new UtcTimestampType());をどこで呼び出す必要がありますか。?
ストーニー

@Stony Hibernate構成を初期化する場所。HibernateUtil(ほとんどの場合)があれば、そこにあります。
シェーン

1
それは機能しますが、確認したところ、timezone = "UTC"で動作し、すべてのデフォルトのタイムスタンプタイプが「タイムスタンプ付きタイムスタンプ」であるpostgresサーバーには必要ないことに気付きました(その後、自動的に実行されます)。ただし、これは完全な単一クラスとしてのHibernate 4.3.5 GAの修正バージョンであり、Spring Factory Beanをオーバーライドしています。pastebin.com
tT4ACXn6

10

この一般的な問題はHibernateによって処理されると思います。しかし、そうではありません!それを正しくするいくつかの「ハック」があります。

私が使用するのは、データベースに日付をLongとして格納することです。したがって、私は常に1/1/70以降のミリ秒で作業しています。次に、日付のみを返す/受け入れるクラスにゲッターとセッターを設定します。したがって、APIは同じままです。マイナス面は、私がデータベースに長いことを持っているということです。SQLを使用したSOでは、ほとんど<、>、=の比較しか行えません。

別のアプローチは、ここで説明されているカスタムマッピングタイプを使用することです:http : //www.hibernate.org/100.html

これに対処する正しい方法は、日付ではなくカレンダーを使用することです。カレンダーを使用すると、永続化する前にTimeZoneを設定できます。

注:愚かなstackoverflowは私にコメントさせないので、ここにデビッドaへの応答があります。

このオブジェクトをシカゴで作成した場合:

new Date(0);

Hibernateはそれを「12/31/1969 18:00:00」として保持します。日付にはタイムゾーンを含めないようにする必要があるため、調整が行われる理由がわかりません。


1
恥ずかしい!あなたは正しかった、そしてあなたの投稿からのリンクはそれをうまく説明しています。今、私は私の答えはいくつかの否定的な評判に値すると思います:)
デビッドa。

1
どういたしまして。これが問題である理由の非常に明確な例を投稿するように勧められました。
codefinger 2009

4
Calendarオブジェクトを使用して時間を正しく保持することができたので、提案したとおり、それらはUTCとしてDBに保存されます。ただし、永続化されたエンティティをデータベースから読み取る場合、Hibernateはそれらがローカルタイムゾーンにあると想定し、Calendarオブジェクトは正しくありません。
John K

1
John K、このCalendar読み取りの問題を解決するために、HibernateまたはJPAは、HibernateがTIMESTAMP列に読み書きする日付を変換するタイムゾーンをマッピングごとに指定する方法を提供する必要があると思います。
Derek Mahar、2010年

joekutner、stackoverflow.com / questions / 4123534 /…を読んだ後Timestamp、JDBCドライバーが日付を格納することを必ずしも信頼できないため、データベースにEpoch以降のミリ秒を格納する必要があるという意見を共有しました私たちは期待するでしょう。
Derek Mahar、

8

ここではいくつかのタイムゾーンが動作しています:

  1. Javaの日付クラス(utilとsql)。UTCの暗黙的なタイムゾーンがあります。
  2. JVMが実行されているタイムゾーン、および
  3. データベースサーバーのデフォルトのタイムゾーン。

これらはすべて異なる場合があります。Hibernate / JPAには重大な設計上の欠陥があり、ユーザーはタイムゾーン情報がデータベースサーバーに保持されていることを簡単に確認できない(JVMで正しい時刻と日付を再構築できる)。

JPA / Hibernateを使用してタイムゾーンを(簡単に)格納する機能がない場合、情報は失われ、情報が失われると、(可能な場合は)構築するのにコストがかかります。

私は常にタイムゾーン情報を保存することをお勧めします(デフォルトである必要があります)。ユーザーは、タイムゾーンを最適化するオプション機能を持つ必要があります(表示にのみ影響しますが、日付には暗黙的なタイムゾーンがまだあります)。

申し訳ありませんが、この投稿は回避策を提供していません(他の場所で回答されています)が、常にタイムゾーン情報を常に保存することが重要である理由の合理化です。残念ながら、多くのコンピュータサイエンティストやプログラミングの専門家は、「情報の損失」の見方や、国際化のようなことを非常に困難にしている理由を理解していないという理由だけでタイムゾーンの必要性に反対しているようです。クライアントと組織内の人々が世界中を移動します。


1
「Hibernate / JPAには設計上の重大な欠陥があります」これはSQLの欠陥であり、これは伝統的にタイムゾーンを暗黙的にすることを可能にし、したがって潜在的に何でもします。愚かなSQL。
Raedwald、2012年

3
実際には、常にタイムゾーンを保存する代わりに、1つのタイムゾーン(通常はUTC)で標準化し、永続化するときにすべてをこのタイムゾーンに変換することもできます(読み取り時に戻すこともできます)。これは私たちが通常行うことです。ただし、JDBCはそれを直接サポートしていません:-/。
sleske

3

Sourceforgeで私のプロジェクトを見てください。このプロジェクトには、標準のSQLの日付と時刻の型、およびJSR 310とJoda Timeのユーザー型があります。すべてのタイプが相殺問題に対処しようとします。http://sourceforge.net/projects/usertype/を参照してください

編集:このコメントに添付されたデレク・マハールの質問に応じて:

「クリス、ユーザータイプはHibernate 3以降で動作しますか?– Derek Mahar Nov 7 '10 at 12:30」

はい、これらのタイプはHibernate 3.6を含むHibernate 3.xバージョンをサポートしています。


2

日付はどのタイムゾーンにもありません(誰にとっても定義された瞬間からのミリ秒単位のオフィスです)が、基盤となる(R)DBは通常、タイムスタンプを政治形式(年、月、日、時間、分、秒など)で格納します。 ..)それはタイムゾーンの影響を受けます。

真剣に、Hibernate 何らかの形式のマッピング内でDBの日付がそのようなタイムゾーンにあることを通知できるようにする必要があります。これにより、Hibernate それをロードまたは保存するときに、それ自体は想定しません。


1

日付をUTCとしてDBに保存しvarchar、明示的なString <-> java.util.Date変換を使用したり、Javaアプリ全体をUTCタイムゾーンに設定したりするときにも同じ問題が発生しました(JVMが多くのアプリケーションで共有されます)。

そのため、オープンソースプロジェクトDbAssistがあり、データベースからの読み取り/書き込みをUTC日付として簡単に修正できます。JPAアノテーションを使用してエンティティのフィールドをマッピングしているので、Maven pomファイルに次の依存関係を含めるだけで済みます。

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

次に@EnableAutoConfiguration、Springアプリケーションクラスの前にアノテーションを追加して、修正(Hibernate + Spring Bootの例の場合)を適用します。他のセットアップのインストール手順やその他の使用例については、プロジェクトのgithubを参照してください。

エンティティを変更する必要がないというのは良いことです。java.util.Dateフィールドをそのままにしておくことができます。

5.2.2使用しているHibernateのバージョンに対応している必要があります。プロジェクトで使用しているバージョンはわかりませんが、提供されている修正の完全なリストは、プロジェクトのgithubの wikiページで入手できます。修正がさまざまなHibernateバージョンで異なるのは、Hibernate作成者がリリース間でAPIを数回変更したためです。

内部的に、修正プログラムは、カスタムを作成するために、ダイベストクリム、シェーン、およびその他のいくつかのソースからのヒントを使用しますUtcDateType。次に、標準java.util.DateUtcDateType、必要なすべてのタイムゾーン処理を処理するカスタムとマッピングします。タイプのマッピングは、@Typedef提供されたpackage-info.javaファイルの注釈を使用して行われます。

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

このようなタイムシフトが発生する理由とそれを解決するためのアプローチについて説明している記事は、こちらにあります。


1

Hibernateでは、注釈やその他の手段でタイムゾーンを指定することはできません。日付の代わりにカレンダーを使用する場合は、HIbernateプロパティAccessTypeを使用して回避策を実装し、マッピングを自分で実装できます。より高度なソリューションは、カスタムUserTypeを実装して日付またはカレンダーをマップすることです。両方のソリューションについては、こちらのブログ投稿で説明されています。http//www.joobik.com/2010/11/mapping-dates-and-time-zones-with.html

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.