Hibernateエンティティのライフサイクル
1. 概要
すべてのHibernateエンティティには、フレームワーク内に当然ライフサイクルがあります。ライフサイクルは、一時的な状態、管理された状態、切り離された状態、または削除された状態のいずれかです。
Hibernateを適切に使用するには、概念レベルと技術レベルの両方でこれらの状態を理解することが不可欠です。
エンティティを処理するさまざまなHibernateメソッドについては、one of our previous tutorialsを参照してください。
2. ヘルパーメソッド
このチュートリアル全体を通して、一貫していくつかのヘルパーメソッドを使用します。
-
HibernateLifecycleUtil.getManagedEntities(session) –これを使用して、Session’s内部ストアからすべての管理対象エンティティを取得します
-
DirtyDataInspector.getDirtyEntities() –このメソッドを使用して、「ダーティ」としてマークされたすべてのエンティティのリストを取得します
-
HibernateLifecycleUtil.queryCount(query) – は、埋め込みデータベースに対してcount(*)クエリを実行するための便利な方法です。
上記のヘルパーメソッドはすべて、読みやすくするために静的にインポートされます。 この記事の最後にリンクされているGitHubプロジェクトで実装を見つけることができます。
3. 永続性のコンテキストがすべてです
エンティティのライフサイクルのトピックに入る前に、まず、the persistence contextを理解する必要があります。
Simply put, the persistence context sits between client code and data store。 これは、永続データがエンティティに変換され、クライアントコードによって読み取られて変更される準備ができているステージング領域です。
理論的には、persistence context はUnit of Workパターンの実装です。 ロードされたすべてのデータを追跡し、そのデータの変更を追跡し、ビジネストランザクションの終了時に最終的にデータベースに変更を同期します。
JPAEntityManagerとHibernateのSession は、persistence context conceptの実装です。 この記事全体を通して、persistence context.を表すためにHibernateのSessionを使用します
Hibernateエンティティのライフサイクル状態は、次に説明するように、エンティティがpersistence contextにどのように関連しているかを説明します。
4. 管理対象エンティティ
A managed entity is a representation of a database table row(ただし、その行はデータベースにまだ存在している必要はありません)。
これは、現在実行中のSessionおよびevery change made on it will be tracked and propagated to the database automaticallyによって管理されます。
Session は、データベースからエンティティをロードするか、切り離されたエンティティを再接続します。 切り離されたエンティティについては、セクション5で説明します。
明確にするためにいくつかのコードを観察してみましょう。
サンプルアプリケーションは、1つのエンティティであるFootballPlayerクラスを定義します。 起動時に、いくつかのサンプルデータを使用してデータストアを初期化します。
+-------------------+-------+
| Name | ID |
+-------------------+-------+
| Cristiano Ronaldo | 1 |
| Lionel Messi | 2 |
| Gianluigi Buffon | 3 |
+-------------------+-------+
最初にブッフォンの名前を変更したいとします。ジジブッフォンの代わりにフルネームGianluigi Buffon を入力します。
まず、Session:を取得して作業単位を開始する必要があります
Session session = sessionFactory.openSession();
サーバー環境では、コンテキストアウェアプロキシを介してコードにSessionを挿入する場合があります。 原則は同じです。作業単位のビジネストランザクションをカプセル化するには、Session が必要です。
次に、永続ストアからデータをロードするようにSessionに指示します。
assertThat(getManagedEntities(session)).isEmpty();
List players = s.createQuery("from FootballPlayer").getResultList();
assertThat(getManagedEntities(session)).size().isEqualTo(3);
最初にSessionを取得したとき、最初のassertステートメントに示されているように、その永続的なコンテキストストアは空です。
次に、データベースからデータを取得し、データのエンティティ表現を作成し、最後に使用するエンティティを返すクエリを実行しています。
内部的には、Session は、永続コンテキストストアにロードするすべてのエンティティを追跡します。 この場合、Session’s 内部ストアにはクエリの後に3つのエンティティが含まれます。
それでは、ジジの名前を変更しましょう。
Transaction transaction = session.getTransaction();
transaction.begin();
FootballPlayer gigiBuffon = players.stream()
.filter(p -> p.getId() == 3)
.findFirst()
.get();
gigiBuffon.setName("Gianluigi Buffon");
transaction.commit();
assertThat(getDirtyEntities()).size().isEqualTo(1);
assertThat(getDirtyEntities().get(0).getName()).isEqualTo("Gianluigi Buffon");
4.1. それはどのように機能しますか?
トランザクションcommit()またはflush()を呼び出すと、Sessionは追跡リストからdirtyエンティティを検索し、状態をデータベースに同期します。
エンティティ内で何かを変更したことをSessionに通知するためにメソッドを呼び出す必要がないことに注意してください。これは管理対象エンティティであるため、すべての変更がデータベースに自動的に反映されます。
管理対象エンティティは常に永続エンティティです。データベース行の表現がまだ作成されていない場合でも、データベース識別子が必要です。 INSERTステートメントは、作業単位の終了を保留しています。
以下の一時的なエンティティに関する章を参照してください。
5. 切り離されたエンティティ
ID値がデータベース行に対応するA detached entity is just an ordinary entity POJO。 管理対象エンティティとの違いは、not tracked anymore by any persistence contextであるということです。
エンティティの読み込みに使用されたSessionが閉じられたとき、またはSession.evict(entity)またはSession.clear()を呼び出すと、エンティティが切り離される可能性があります。
コードでそれを見てみましょう:
FootballPlayer cr7 = session.get(FootballPlayer.class, 1L);
assertThat(getManagedEntities(session)).size().isEqualTo(1);
assertThat(getManagedEntities(session).get(0).getId()).isEqualTo(cr7.getId());
session.evict(cr7);
assertThat(getManagedEntities(session)).size().isEqualTo(0);
永続コンテキストは、分離されたエンティティの変更を追跡しません。
cr7.setName("CR7");
transaction.commit();
assertThat(getDirtyEntities()).isEmpty();
Session.merge(entity)/Session.update(entity) can (re)セッションをアタッチします:
FootballPlayer messi = session.get(FootballPlayer.class, 2L);
session.evict(messi);
messi.setName("Leo Messi");
transaction.commit();
assertThat(getDirtyEntities()).isEmpty();
transaction = startTransaction(session);
session.update(messi);
transaction.commit();
assertThat(getDirtyEntities()).size().isEqualTo(1);
assertThat(getDirtyEntities().get(0).getName()).isEqualTo("Leo Messi");
Session.merge()とSession.update()の両方のリファレンスについては、hereを参照してください。
5.1. アイデンティティフィールドが重要です
次のロジックを見てみましょう。
FootballPlayer gigi = new FootballPlayer();
gigi.setId(3);
gigi.setName("Gigi the Legend");
session.update(gigi);
上記の例では、コンストラクタを介して通常の方法でエンティティをインスタンス化しました。 フィールドに値を入力し、IDを3に設定しました。これは、GigiBuffonに属する永続データのIDに対応します。 update()を呼び出すと、別のpersistence contextからエンティティを読み込んだ場合とまったく同じ効果があります。
実際、Session は、再接続されたエンティティがどこから発生したかを区別しません。
HTMLフォーム値からデタッチされたエンティティを構築することは、ウェブアプリケーションでは非常に一般的なシナリオです。
Sessionに関する限り、デタッチされたエンティティは、ID値が永続データに対応する単なるプレーンエンティティです。
上記の例は、デモ目的にすぎないことに注意してください。 何をしているのかを正確に知る必要があります。 そうしないと、更新するフィールドに値を設定するだけで、残りはそのまま(実質的にnull)のままにしておくと、エンティティ全体でnull値になる可能性があります。
6. 一時的なエンティティ
A transient entity is simply an entity object that has no representation in the persistent storeであり、Sessionによって管理されていません。
一時的なエンティティの典型的な例は、コンストラクタを介して新しいエンティティをインスタンス化することです。
一時的なエンティティをpersistentにするには、Session.save(entity) またはSession.saveOrUpdate(entity):を呼び出す必要があります
FootballPlayer neymar = new FootballPlayer();
neymar.setName("Neymar");
session.save(neymar);
assertThat(getManagedEntities(session)).size().isEqualTo(1);
assertThat(neymar.getId()).isNotNull();
int count = queryCount("select count(*) from Football_Player where name='Neymar'");
assertThat(count).isEqualTo(0);
transaction.commit();
count = queryCount("select count(*) from Football_Player where name='Neymar'");
assertThat(count).isEqualTo(1);
Session.save(entity)を実行するとすぐに、エンティティにID値が割り当てられ、Sessionによって管理されるようになります。 ただし、作業単位が終了するまでINSERT操作が遅延する可能性があるため、データベースでまだ使用できない可能性があります。
7. 削除されたエンティティ
An entity is in a deleted (removed) state if Session.delete(entity)が呼び出され、Session hasがエンティティに削除のマークを付けました。 DELETEコマンド自体は、作業単位の最後に発行される場合があります。
次のコードでそれを見てみましょう:
session.delete(neymar);
assertThat(getManagedEntities(session).get(0).getStatus()).isEqualTo(Status.DELETED);
ただし、エンティティは作業ユニットの終了まで永続コンテキストストアにとどまることに注意してください。
8. 結論
persistence contextの概念は、Hibernateエンティティのライフサイクルを理解する上で中心的な役割を果たします。 各ステータスを示すコード例を調べることで、ライフサイクルを明確にしました。
いつものように、この記事で使用されているコードはover on GitHubにあります。