web-dev-qa-db-ja.com

JPAおよびHibernateでUTCタイムゾーンに日付/時刻とタイムスタンプを保存する方法

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

public class Event {
    @Id
    public int id;

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

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

93
Steve Kuo

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

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

詳細については、 この記事 をご覧ください。

50
Vlad Mihalcea

私の知る限り、JavaアプリをUTCタイムゾーンに配置する必要があります(Hibernateが日付をUTCで保存するため)、必要なときにタイムゾーンに変換する必要がありますものを表示します(少なくともこの方法で行います)。

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

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

そして、希望するタイムゾーンをDateFormatに設定します。

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

HibernateはDatesのタイムゾーンを無視します(何もないため)が、実際には問題を引き起こしているのはJDBCレイヤーです。 ResultSet.getTimestampPreparedStatement.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として処理されます。 [まだテストしていません]

41
divestoclimb

completelyに基づく回答を追加し、Shaun Stoneのヒントを得て、ディベストクライムに恩恵をもたらしました。よくある問題であり、解決策は少しわかりにくいので、詳細に説明したかっただけです。

これは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 <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 );
            }
        };
    }
}

次に、UtcTimestampTypeを作成します。これは、スーパーコンストラクター呼び出しでSqlTypeDescriptorとしてTimestampTypeDescriptorの代わりにUtcTimestampTypeDescriptorを使用しますが、それ以外はすべて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。

11
Shane

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

私が使用するのは、日付をデータベースにLongとして保存することです。したがって、私は常に1/1/70以降のミリ秒で作業しています。その後、クラスにゲッターとセッターがあり、日付のみを返す/受け入れる。したがって、APIは同じままです。欠点は、データベースに長所があることです。 SO SQLを使用すると、<、>、=の比較のみを行うことができます。派手な日付演算子は使用できません。

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

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

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

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

new Date(0);

Hibernateは「12/31/1969 18:00:00」として永続化します。日付にはタイムゾーンがないはずなので、なぜ調整が行われるのか分かりません。

10
codefinger

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

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

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

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

私は常にタイムゾーン情報を保存する方が良いと主張します(デフォルトである必要があります)、ユーザーはタイムゾーンを最適化するオプションの機能を持っている必要があります(実際に表示に影響するだけですが、どの日付にも暗黙のタイムゾーンがあります)。

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

8
Moa

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

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

次に、Entityクラスファイルで、

@Column
private LocalDateTime created;
6
JavaGeek

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

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

「クリス、ユーザータイプはHibernate 3以上で動作しますか?-デレクマハル10年11月7日12:30に」

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

3
Chris Pheby

日付はどのタイムゾーンにもありません(定義された瞬間から全員に同じミリ秒のオフィスです)が、基礎となる(R)DBは通常、タイムスタンプを政治形式(年、月、日、時間、分、秒、...)で保存します..)時間帯に敏感です。

深刻なことに、Hibernate [〜#〜] must [〜#〜]は、何らかの形式のマッピング内でDB日付がそのようなタイムゾーンにあることを通知できるようにします。それを保存します、それはそれ自身を仮定しません...

2
Time Master

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

1
joobik

日付を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>

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

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

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

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

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

あなたは記事を見つけることができます こちら これはなぜそのような時間シフトが起こるのか、そしてそれを解決するためのアプローチは何かを説明しています。

1
orim