HibernateおよびMySQLでの作成タイムスタンプと最終更新タイムスタンプ


244

特定のHibernateエンティティについては、その作成時刻と最後に更新された時刻を保存する必要があります。これをどのように設計しますか?

  • データベースでどのデータ型を使用しますか(MySQLの場合、JVMとは異なるタイムゾーンの可能性があります)?データ型はタイムゾーンを認識しますか?

  • あなたはJavaでどのようなデータ型を使用しますか(DateCalendarlong、...)?

  • データベース、ORMフレームワーク(Hibernate)、またはアプリケーションプログラマーの誰がタイムスタンプの設定を担当しますか?

  • マッピングにはどのアノテーションを使用し@Temporalますか(例:)?

実用的なソリューションだけでなく、安全で適切に設計されたソリューションも探しています。

回答:


266

JPAアノテーションを使用している場合@PrePersist@PreUpdateイベントフックを使用してこれを行うことができます。

@Entity
@Table(name = "entities")    
public class Entity {
  ...

  private Date created;
  private Date updated;

  @PrePersist
  protected void onCreate() {
    created = new Date();
  }

  @PreUpdate
  protected void onUpdate() {
    updated = new Date();
  }
}

または@EntityListener、クラスのアノテーションを使用して、イベントコードを外部クラスに配置することもできます。


7
@PrePersistと@PerUpdateはJPAアノテーションであるため、J2SEでは問題なく動作します。
Kdeveloper 2010

2
@Kumar-(JPAの代わりに)プレーンのHibernateセッションを使用している場合は、JPAアノテーションに対して非常にエレガントでコンパクトではありませんが、休止イベントリスナーを試すことができます。
Shailendra 2013年

43
JPAを使用する現在のHibernateでは、「@ CreationTimestamp」と「@UpdateTimestamp」を使用できます
Florian Loch

@FlorianLochタイムスタンプではなく日付に相当するものはありますか?それとも自分で作成する必要がありますか?
マイク2016年

150

あなただけ使用することができます@CreationTimestamp@UpdateTimestamp

@CreationTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "create_date")
private Date createDate;

@UpdateTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "modify_date")
private Date modifyDate;

3
タイムスタンプを更新する必要があるような小さなことをありがとう。知らなかった。あなたは私の日を救った。
Virendra Sagar 2017

あるTemporalType.DATE最初のケースではとTemporalType.TIMESTAMP二番目に。
v.ladynev 2017

これも自動的に値を設定すると言っていますか?それは私の経験ではありません。それもしているようだ@CreationTimestamp@UpdateTimestamp1はどちらかのいくつかを必要とする@Column(..., columnDefinition = "timestamp default current_timestamp")、または使用@PrePersistして@PreUpdate(後者もうまく確保し、クライアントが別の値を設定することはできません)。
Arjan

2
オブジェクトを更新して永続化すると、BDはcreate_dateを失いました...なぜですか?
Brenno Leal

1
私のケースnullable=false@Column(name = "create_date" , nullable=false)作業から削除されました
Shantaram Tupe

113

この投稿のリソースと、さまざまなソースから左右に取得した情報を利用して、このエレガントなソリューションを思い付き、次の抽象クラスを作成しました

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@MappedSuperclass
public abstract class AbstractTimestampEntity {

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created", nullable = false)
    private Date created;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "updated", nullable = false)
    private Date updated;

    @PrePersist
    protected void onCreate() {
    updated = created = new Date();
    }

    @PreUpdate
    protected void onUpdate() {
    updated = new Date();
    }
}

そして、あなたのすべてのエンティティにそれを拡張させます、例えば:

@Entity
@Table(name = "campaign")
public class Campaign extends AbstractTimestampEntity implements Serializable {
...
}

5
これは、エンティティにさまざまな排他的な動作を追加するまでは有効です(複数の基本クラスを拡張することはできません)。基本クラスなしで同じ効果を得る唯一の方法は、aspectj itdまたはイベントリスナーが@kieren dixonの回答を参照することです
gpilotino

3
MySQLトリガーを使用してこれを行うので、エンティティ全体が保存されなかったり、外部アプリケーションや手動クエリによって変更されたりしても、これらのフィールドは更新されます。
Webnet 2013

3
例外が発生しているため、not-null property references a null or transient value: package.path.ClassName.created
実際に役立つ

@rishiAgar、いいえ。しかし今のところ、デフォルトのコンストラクタからプロパティに日付を割り当てています。見つけたらお知らせします。
Sumit Ramteke 2014

1
それを動作@Column(name = "updated", nullable = false, insertable = false)させるために変更してください。この答えは非常に多くのupvotesを得たことは興味深い。..
のDisplayName

20

1.使用する必要があるデータベース列のタイプ

最初の質問は:

データベースでどのデータ型を使用しますか(MySQLの場合、JVMとは異なるタイムゾーンの可能性があります)?データ型はタイムゾーンを認識しますか?

MySQLでは、TIMESTAMPカラムタイプはJDBCドライバーのローカルタイムゾーンからデータベースのタイムゾーンにシフトしますが'2038-01-19 03:14:07.999999、最大でまでのタイムスタンプしか保存できないため、将来的には最適ではありません。

そのため、DATETIMEこの上限の制限がない代わりに使用することをお勧めします。ただし、DATETIMEタイムゾーンは認識されません。したがって、このため、データベース側でUTCを使用し、hibernate.jdbc.time_zoneHibernateプロパティを使用するのが最善です。

hibernate.jdbc.time_zone設定の詳細については、こちらの記事をご覧ください。

2.使用する必要があるエンティティプロパティタイプ

2つ目の質問は次のとおりです。

Javaではどのデータ型を使用しますか(日付、カレンダー、ロングなど)?

Java側では、Java 8を使用できますLocalDateTime。レガシーを使用することもできますDateが、Java 8の日付/時刻型は不変であり、ログに記録するときにタイムゾーンをローカルタイムゾーンにシフトしないため、より優れています。

HibernateでサポートされているJava 8の日付/時刻型の詳細については、こちらの記事をご覧ください。

これで、この質問にも答えることができます。

マッピングにはどのアノテーションを使用し@Temporalますか(例:)?

LocalDateTimeまたはjava.sql.Timestampを使用してタイムスタンプエンティティプロパティをマップする場合@Temporal、HIbernateはこのプロパティがJDBCタイムスタンプとして保存されることをすでに認識しているため、使用する必要はありません。

を使用している場合のみ、次のように注釈java.util.Dateを指定する必要があります@Temporal

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_on")
private Date createdOn;

しかし、次のようにマッピングすると、はるかに優れています。

@Column(name = "created_on")
private LocalDateTime createdOn;

監査列の値を生成する方法

3つ目の質問は次のとおりです。

データベース、ORMフレームワーク(Hibernate)、またはアプリケーションプログラマーの誰がタイムスタンプの設定を担当しますか?

マッピングにはどのアノテーションを使用しますか(例:@Temporal)?

この目標を達成する方法はたくさんあります。データベースにそれを許可することができます。

以下のためにcreate_on列に、あなたが使用することができますDEFAULTように、DDL制約を:

ALTER TABLE post 
ADD CONSTRAINT created_on_default 
DEFAULT CURRENT_TIMESTAMP() FOR created_on;

updated_on列については、DBトリガーを使用してCURRENT_TIMESTAMP()、特定の行が変更されるたびに列の値を設定できます。

または、JPAまたはHibernateを使用して設定します。

次のデータベーステーブルがあるとします。

監査列を含むデータベーステーブル

そして、各テーブルには次のような列があります。

  • created_by
  • created_on
  • updated_by
  • updated_on

Hibernate @CreationTimestamp@UpdateTimestampアノテーションの使用

Hibernateは、および列のマッピングに使用できる@CreationTimestampおよび@UpdateTimestampアノテーションを提供します。created_onupdated_on

を使用@MappedSuperclassして、すべてのエンティティによって拡張される基本クラスを定義できます。

@MappedSuperclass
public class BaseEntity {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "created_on")
    @CreationTimestamp
    private LocalDateTime createdOn;

    @Column(name = "created_by")
    private String createdBy;

    @Column(name = "updated_on")
    @UpdateTimestamp
    private LocalDateTime updatedOn;

    @Column(name = "updated_by")
    private String updatedBy;

    //Getters and setters omitted for brevity
}

そして、すべてのエンティティは次のBaseEntityようにを拡張します。

@Entity(name = "Post")
@Table(name = "post")
public class Post extend BaseEntity {

    private String title;

    @OneToMany(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List<PostComment> comments = new ArrayList<>();

    @OneToOne(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true,
        fetch = FetchType.LAZY
    )
    private PostDetails details;

    @ManyToMany
    @JoinTable(
        name = "post_tag",
        joinColumns = @JoinColumn(
            name = "post_id"
        ),
        inverseJoinColumns = @JoinColumn(
            name = "tag_id"
        )
    )
    private List<Tag> tags = new ArrayList<>();

    //Getters and setters omitted for brevity
}

の使用について詳しくは@MappedSuperclassこちらの記事をご覧ください。

しかし、場合でもcreatedOn及びupdateOn特性は、Hibernateの固有によって設定される@CreationTimestamp@UpdateTimestamp、注釈createdBy及びupdatedBy以下JPA溶液によって示されるように、アプリケーションのコールバックを登録する必要があります。

JPAの使用 @EntityListeners

監査プロパティをEmbeddableにカプセル化できます。

@Embeddable
public class Audit {

    @Column(name = "created_on")
    private LocalDateTime createdOn;

    @Column(name = "created_by")
    private String createdBy;

    @Column(name = "updated_on")
    private LocalDateTime updatedOn;

    @Column(name = "updated_by")
    private String updatedBy;

    //Getters and setters omitted for brevity
}

そして、AuditListener監査プロパティを設定するを作成します。

public class AuditListener {

    @PrePersist
    public void setCreatedOn(Auditable auditable) {
        Audit audit = auditable.getAudit();

        if(audit == null) {
            audit = new Audit();
            auditable.setAudit(audit);
        }

        audit.setCreatedOn(LocalDateTime.now());
        audit.setCreatedBy(LoggedUser.get());
    }

    @PreUpdate
    public void setUpdatedOn(Auditable auditable) {
        Audit audit = auditable.getAudit();

        audit.setUpdatedOn(LocalDateTime.now());
        audit.setUpdatedBy(LoggedUser.get());
    }
}

を登録するAuditListenerには、@EntityListenersJPAアノテーションを使用できます。

@Entity(name = "Post")
@Table(name = "post")
@EntityListeners(AuditListener.class)
public class Post implements Auditable {

    @Id
    private Long id;

    @Embedded
    private Audit audit;

    private String title;

    @OneToMany(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List<PostComment> comments = new ArrayList<>();

    @OneToOne(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true,
        fetch = FetchType.LAZY
    )
    private PostDetails details;

    @ManyToMany
    @JoinTable(
        name = "post_tag",
        joinColumns = @JoinColumn(
            name = "post_id"
        ),
        inverseJoinColumns = @JoinColumn(
            name = "tag_id"
        )
    )
    private List<Tag> tags = new ArrayList<>();

    //Getters and setters omitted for brevity
}

JPAでの監査プロパティの実装の詳細については@EntityListener、チェックアウトこの記事を


非常に完全な答え、ありがとう。私が好むについて同意datetimeを超えますtimestamp。タイムスタンプのタイムゾーンをデータベースに認識させる必要があります。これにより、タイムゾーン変換エラーが防止されます。
Ole VV

timestsmpタイプは、タイムゾーン情報を格納しません。アプリTZからDB TZへの会話を行うだけです。実際には、クライアントTZを個別に保存し、UIをレンダリングする前にアプリケーションで会話を行います。
Vlad Mihalcea

正しい。MySQL timestampは常にUTCです。MySQL TIMESTAMPは、値を現在のタイムゾーンからUTCに変換して格納し、取得のためにUTCから現在のタイムゾーンに戻します。 MySQLドキュメント:DATE、DATETIME、およびTIMESTAMPタイプ
Ole VV

17

インターセプターを使用して値を設定することもできます

エンティティが実装するTimeStampedというインターフェースを作成します

public interface TimeStamped {
    public Date getCreatedDate();
    public void setCreatedDate(Date createdDate);
    public Date getLastUpdated();
    public void setLastUpdated(Date lastUpdatedDate);
}

インターセプターを定義する

public class TimeStampInterceptor extends EmptyInterceptor {

    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, 
            Object[] previousState, String[] propertyNames, Type[] types) {
        if (entity instanceof TimeStamped) {
            int indexOf = ArrayUtils.indexOf(propertyNames, "lastUpdated");
            currentState[indexOf] = new Date();
            return true;
        }
        return false;
    }

    public boolean onSave(Object entity, Serializable id, Object[] state, 
            String[] propertyNames, Type[] types) {
            if (entity instanceof TimeStamped) {
                int indexOf = ArrayUtils.indexOf(propertyNames, "createdDate");
                state[indexOf] = new Date();
                return true;
            }
            return false;
    }
}

そしてそれをセッションファクトリーに登録します



EntityManagerの代わりにSessionFactoryを使用する場合、これは1つの解決策です!
olivmir 2018

このコンテキストで私が行ったのと同じような問題に苦しむ人のためだけに:エンティティ自体がこれらの追加フィールド(createdAt、...)を定義せず、親クラスから継承する場合、この親クラスに注釈を付ける必要があります@MappedSuperclass-それ以外の場合、Hibernateはこれらのフィールドを検出しません。
olivmir 2018

17

Olivierのソリューションでは、更新ステートメントの実行中に次のことが発生する可能性があります。

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:列 'created'をnullにすることはできません

これを解決するには、 "created"属性の@Columnアノテーションにupdatable = falseを追加します。

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created", nullable = false, updatable=false)
private Date created;

1
使用してい@Versionます。エンティティが設定されると、2つの呼び出しが行われ、1つは保存、もう1つは更新されます。このため、同じ問題に直面していました。追加する@Column(updatable = false)と問題が解決しました。
Ganesh Satpute

12

助けてくれたみんなに感謝します。自分でいくつかの調査を行った後(私が質問したのは私です)、ここで私が最も理解できることがわかりました。

  • データベース列タイプ:decimal(20)1 ^ 70からのタイムゾーンに依存しないミリ秒数。2^ 64は20桁で、ディスク容量が少ないため、次のように表されます。簡単にしましょう。また、DEFAULT CURRENT_TIMESTAMPもトリガーも使用しません。DBに魔法は必要ありません。

  • Javaフィールドタイプ:long。Unixタイムスタンプはさまざまなライブラリで十分にサポートされてlongおり、Y2038の問題はありません。タイムスタンプの計算は高速で簡単です(主に演算子<と演算子+、計算に日/月/年が含まれないと仮定しています)。そして最も重要なのは、プリミティブlongjava.lang.Longの両方が不変であり、実質的にsとは異なり値によって渡されることjava.util.Dateです。foo.getLastUpdate().setTime(System.currentTimeMillis())他の誰かのコードをデバッグするときのようなものを見つけるのに本当に腹が立つでしょう。

  • ORMフレームワークは、データを自動的に入力する必要があります。

  • 私はまだこれをテストしていませんが、私がそれで@Temporalうまくいくと想定しているドキュメントのみを調べています。@Versionこの目的で使用するかどうかはわかりません。 @PrePersistそして@PreUpdateそれを手動で制御するための良い代替手段です。すべてのエンティティのための層のスーパータイプ(共通の基底クラス)にそれを追加し、あなたが本当にのためのタイムスタンプをしたいというキュートなアイデアが提供され、すべてのあなたのエンティティの。


longsとLongsは不変かもしれませんが、あなたが説明する状況では役に立ちません。彼らはまだfoo.setLastUpdate(new Long(System.currentTimeMillis());と言うことができます)
Ian McLaird

2
それはいいです。とにかくHibernateはセッターを必要とします(またはリフレクションを介してフィールドに直接アクセスしようとします)。私は、アプリケーションコードからタイムスタンプを変更している人物を追跡するのが難しいことについて話していました。ゲッターを使用してそれを行うことができる場合は注意が必要です。
ngn 2008年

ORMフレームワークが日付を自動的に入力する必要があるというあなたの主張に同意しますが、さらに一歩進んで、日付はクライアントではなくデータベースサーバーのクロックから設定する必要があると言います。これでこの目的が達成されるかどうかはわかりません。SQLでは、sysdate関数を使用してこれを行うことができますが、HibernateまたはJPA実装でこれを行う方法がわかりません。
MiguelMunoz

DBに魔法は必要ありません。私はあなたの意味を理解していますが、データベースが悪い/新規/無知な開発者からそれ自体を保護すべきであるという事実を考慮したいと思います。大企業ではデータの整合性が非常に重要です。適切なデータを挿入するために他人に頼ることはできません。制約、デフォルト、およびFKは、それを達成するのに役立ちます。
アイスグラス2017

6

Session APIを使用している場合、PrePersistおよびPreUpdateコールバックはこの回答に従って機能しません。

私はコードでHibernate Sessionのpersist()メソッドを使用しているため、この作業を行う唯一の方法は、以下のコードとこのブログ投稿回答にも投稿されています)を使用することでした。

@MappedSuperclass
public abstract class AbstractTimestampEntity {

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created")
    private Date created=new Date();

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "updated")
    @Version
    private Date updated;

    public Date getCreated() {
        return created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }

    public Date getUpdated() {
        return updated;
    }

    public void setUpdated(Date updated) {
        this.updated = updated;
    }
}

updated.clone()他のコンポーネントが内部状態(日付)を操作できるように、クローンされたオブジェクトを返す必要があります
1ambda 2017


3

次のコードは私のために働いた。

package com.my.backend.models;

import java.util.Date;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

import com.fasterxml.jackson.annotation.JsonIgnore;

import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import lombok.Getter;
import lombok.Setter;

@MappedSuperclass
@Getter @Setter
public class BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Integer id;

    @CreationTimestamp
    @ColumnDefault("CURRENT_TIMESTAMP")
    protected Date createdAt;

    @UpdateTimestamp
    @ColumnDefault("CURRENT_TIMESTAMP")
    protected Date updatedAt;
}

こんにちは、なぜ我々は、必要があるだろうprotected Integer id;protected私はとして私のテストケースでそれを使用できなかったため、一般的には親クラスで.getId()
SHAREEF

2

すべてのエンティティに共通の基本クラスを用意することをお勧めします。この基本クラスでは、すべてのエンティティ(一般的なデザイン)、作成、および最終更新日のプロパティで一般的に名前が付けられている場合、idプロパティを使用できます。

作成日は、java.util.Dateプロパティを保持するだけです。常にnew Date()で初期化してください。

最後の更新フィールドには、Timestampプロパティを使用できます。@ Versionでマップする必要があります。このアノテーションにより、プロパティはHibernateによって自動的に更新されます。Hibernateは楽観的ロックも適用することに注意してください(これは良いことです)。


2
楽観的ロックにタイムスタンプ列を使用することは悪い考えです。常に整数バージョンの列を使用します。理由は、2つのJVMが異なる時間にあり、ミリ秒の精度がない可能性があるためです。代わりにhibernateにDBタイムスタンプを使用させると、DBからの追加の選択を意味します。代わりに、バージョン番号を使用してください。
sethu

2

強調するだけです:java.util.CalenderTimestampsではありませんjava.util.Date一時的なもので、タイムゾーンなどの地域的な事柄にとらわれません。ほとんどのデータベースは、この方法で物事を保存します(そうでない場合でも、これは通常、クライアントソフトウェアのタイムゾーン設定です。データは良好です)


1

JAVAのデータ型として、java.util.Dateを使用することを強くお勧めします。カレンダーを使用すると、かなり厄介なタイムゾーンの問題が発生しました。このスレッドを参照してください。

タイムスタンプを設定するには、AOPアプローチを使用するか、テーブルでトリガーを使用することをお勧めします(実際には、これがトリガーの使用を許容できると思う唯一のものです)。


1

時間をDateTimeとして保存し、UTCで保存することもできます。MySqlは日付をUTCに変換し、データを格納および取得するときに現地時間に戻すため、通常はTimestampではなくDateTimeを使用します。そのようなロジックは1か所(ビジネスレイヤー)に保持したいと思います。Timestampを使用する方が望ましい状況は他にもあると思います。


1

私たちにも同じような状況がありました。Mysql 5.7を使用していました。

CREATE TABLE my_table (
        ...
      updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    );

これでうまくいきました。


また、データベース内のSQLクエリによってデータが直接変更された場合にも機能します。そのようなケースはカバーしない@PrePersist@PrePersistください。
pidabrow

1

メソッドで@Transactionalを使用している場合、@ CreationTimestampと@UpdateTimestampはDBに値を保存しますが、save(...)の使用後にnullを返します。

この状況で、saveAndFlush(...)を使用してトリックを行いました


0

Javaコードではこれを行わない方がいいと思います。MySqlテーブル定義で列のデフォルト値を設定するだけです。 ここに画像の説明を入力してください

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