次のエンティティクラスがあります。
@Entity
public class Event {
private OffsetDateTime startDateTime;
// ...
}
ただし、JPA 2.2を使用してデータベースとの間でエンティティを永続化してから読み取ると、情報が失われます:ZoneOffset
のstartDateTime
がUTC
(データベースのタイムスタンプで使用されるZoneOffset
)。例えば:
Event e = new Event();
e.setStartDateTime(OffsetDateTime.parse("2018-01-02T09:00-05:00"));
e.getStartDateTime().getHour(); // 9
e.getStartDateTime().getOffset(); // ZoneOffset.of("-05:00")
// ...
entityManager.persist(e); // Stored startDateTime as timestamp 2018-01-02T14:00Z
Event other = entityManager.find(Event.class, e.getId());
other.getStartDateTime().getHour(); // 14 - DIFFERENT
other.getStartDateTime().getOffset(); // ZoneOffset.of("+00:00") - DIFFERENT
OffsetDateTime
を使用する必要があります。ゾーンルールが変更されるため、ZonedDateTime
は使用できません(とにかくこの情報の損失も発生します)。 LocalDateTime
は世界中のどこででも発生するため、Event
を使用することはできません。精度上の理由から、元のZoneOffset
が必要です。ユーザーがイベントの開始時刻を入力するため、Instant
を使用できません(イベントは予定のようなものです)。
要件:
JPA-QLのタイムスタンプで>, <, >=, <=, ==, !=
比較を実行できる必要がある
永続化されたZoneOffset
と同じOffsetDateTime
を取得できる必要がある
//編集:JPAバージョン2.1と2.2の違いを反映するように回答を更新しました。
//編集2:JPA 2.2仕様のリンクを追加
JPA v2.1はJava 8タイプを認識せず、提供された値を文字列化しようとします。LocalDateTime、InstantおよびOffsetDateTimeの場合、toString()メソッドを使用し、対応する文字列をターゲットフィールド。
つまり、Java.sql.Date
やJava.sql.Timestamp
など、対応するデータベースタイプに値を変換する方法をJPAに伝える必要があります。
AttributeConverter
インターフェイスを実装および登録して、これを機能させます。
見る:
Adam Bienの誤った実装に注意してください。最初にLocalDateをゾーニングする必要があります。
属性コンバータを作成しないでください。それらはすでに含まれています。
//アップデート2:
これは次の仕様で確認できます: JPA 2.2 spec 。最後のページまでスクロールして、時間タイプが含まれていることを確認します。
Jpql式を使用する場合は、必ずInstantオブジェクトを使用し、PDOクラスでもInstantを使用してください。
例えば.
// query does not make any sense, probably.
query.setParameter("createdOnBefore", Instant.now());
これはうまく機能します。
Java.time.Instant
を使用するとにかく、たとえZonedDateTimeまたはOffsetDateTimeがあったとしても、データベースはタイムゾーンに関係なく瞬間を保存するため、データベースから読み取られる結果は常にUTCです。 タイムゾーンは、実際には情報(メタデータ)を表示するだけです。
したがって、代わりにInstant
を使用し、必要な場合にのみZonedまたはOffset Timeクラスに変換することをお勧めします。特定のゾーンまたはオフセットで時刻を復元するには、ゾーンまたはオフセットを独自のデータベースフィールドに個別に保存します。
JPQLの比較は、このソリューションで機能します。常にインスタントを使用し続けるだけです。
PS:最近、私はいくつかのSpringの人たちと話をしましたが、彼らはあなたがInstant以外のものを永続化しないことにも同意しました。インスタントだけが特定の時点であり、メタデータを使用して変換できます。
仕様によると JPA 2.2仕様 、CompositeValuesは言及されていません。つまり、仕様に組み込まれておらず、現時点では単一のフィールドを複数のデータベース列に永続化することはできません。 「複合」を検索し、IDに関連する言及のみを表示します。
ただし、この回答のこのコメントで述べられているように、Hibernateはこれを実行できる可能性があります。
この原則を念頭に置いてこの例を作成しました。拡張のために開き、変更のために閉じます。この原則の詳細については、こちらをご覧ください。 Wikipediaのオープン/クローズド原則 。
これは、現在のフィールドをデータベース(タイムスタンプ)に保持でき、追加の列を追加するだけで、害を与えないことを意味します。
また、エンティティはOffsetDateTimeのセッターとゲッターを保持できます。内部構造は、呼び出し元の問題ではありません。これは、この提案がAPIをまったく傷つけないことを意味します。
実装は次のようになります。
@Entity
public class UserPdo {
@Column(name = "created_on")
private Instant createdOn;
@Column(name = "display_offset")
private int offset;
public void setCreatedOn(final Instant newInstant) {
this.createdOn = newInstant;
this.offset = 0;
}
public void setCreatedOn(final OffsetDateTime dt) {
this.createdOn = dt.toInstant();
this.offset = dt.getOffset().getTotalSeconds();
}
// derived display value
public OffsetDateTime getCreatedOnOnOffset() {
ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(this.offset);
return this.createdOn.atOffset(zoneOffset);
}
}