Hibernateエンティティのライフサイクル
1. 概要
すべてのHibernateエンティティには、当然、フレームワーク内にライフサイクルがあります。これは、一時的な状態、管理された状態、切り離された状態、または削除された状態のいずれかです。
Hibernateを適切に使用するには、概念レベルと技術レベルの両方でこれらの状態を理解することが不可欠です。
エンティティを処理するさまざまなHibernateメソッドについて学ぶには、以前のチュートリアルの1つを参照してください。
2. ヘルパーメソッド
このチュートリアル全体を通して、一貫していくつかのヘルパーメソッドを使用します。
- HibernateLifecycleUtil.getManagedEntities(session)– これを使用して、Sessionの内部ストアからすべての管理対象エンティティを取得します
- DirtyDataInspector.getDirtyEntities()– このメソッドを使用して、「ダーティ」とマークされたすべてのエンティティのリストを取得します
- HibernateLifecycleUtil.queryCount(query)–組み込みデータベースに対してcount(*)クエリを実行するための便利なメソッド
上記のヘルパーメソッドはすべて、読みやすくするために静的にインポートされます。 それらの実装は、この記事の最後にリンクされているGitHubプロジェクトにあります。
3. 永続性コンテキストがすべてです
エンティティのライフサイクルのトピックに入る前に、まず、永続性コンテキストを理解する必要があります。
簡単に言えば、永続コンテキストはクライアントコードとデータストアの間にあります。 これは、永続データがエンティティに変換され、クライアントコードによって読み取られて変更される準備ができているステージング領域です。
理論的には、永続コンテキストは、作業単位パターンの実装です。 ロードされたすべてのデータを追跡し、そのデータの変更を追跡し、ビジネストランザクションの終了時に最終的に変更をデータベースに同期する責任があります。
JPA EntityManager とHibernateのセッションは、永続コンテキストコンセプトの実装です。 この記事全体を通して、Hibernateセッションを使用して永続コンテキストを表します。
Hibernateエンティティのライフサイクル状態は、次に説明するように、エンティティが永続コンテキストにどのように関連しているかを説明します。
4. 管理対象エンティティ
管理対象エンティティはデータベーステーブル行の表現です(ただし、その行はデータベースにまだ存在している必要はありません)。
これは、現在実行中のセッションによって管理され、それに加えられたすべての変更は追跡され、データベースに自動的に伝播されます。
セッションは、データベースからエンティティをロードするか、切り離されたエンティティを再接続します。 切り離されたエンティティについては、セクション5で説明します。
明確にするためにいくつかのコードを観察してみましょう。
サンプルアプリケーションは、FootballPlayerクラスという1つのエンティティを定義します。 起動時に、いくつかのサンプルデータを使用してデータストアを初期化します。
+-------------------+-------+
| Name | ID |
+-------------------+-------+
| Cristiano Ronaldo | 1 |
| Lionel Messi | 2 |
| Gianluigi Buffon | 3 |
+-------------------+-------+
最初にブッフォンの名前を変更したいとします。ジジ・ブッフォンの代わりに、彼のフルネームジャンルイジ・ブッフォンを入力します。
まず、 Session:を取得して、作業単位を開始する必要があります。
Session session = sessionFactory.openSession();
サーバー環境では、コンテキスト認識プロキシを介してSessionをコードに挿入する場合があります。 原則は同じです。作業単位のビジネストランザクションをカプセル化するには、セッションが必要です。
次に、Sessionに永続ストアからデータをロードするように指示します。
assertThat(getManagedEntities(session)).isEmpty();
List<FootballPlayer> players = s.createQuery("from FootballPlayer").getResultList();
assertThat(getManagedEntities(session)).size().isEqualTo(3);
最初にSessionを取得すると、最初の assert ステートメントに示されているように、その永続コンテキストストアは空になります。
次に、データベースからデータを取得し、データのエンティティ表現を作成し、最後に使用するエンティティを返すクエリを実行しています。
内部的には、セッションは、永続コンテキストストアにロードするすべてのエンティティを追跡します。 この場合、セッションの内部ストアには、クエリの後に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値がデータベース行に対応する通常のエンティティPOJOです。 管理対象エンティティとの違いは、永続コンテキストによって追跡されなくなったことです。
エンティティのロードに使用された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)は、セッションを(再)接続できます:
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()の両方のリファレンスについては、こちらを参照してください。
5.1. アイデンティティフィールドが重要
次のロジックを見てみましょう。
FootballPlayer gigi = new FootballPlayer();
gigi.setId(3);
gigi.setName("Gigi the Legend");
session.update(gigi);
上記の例では、コンストラクターを介して通常の方法でエンティティをインスタンス化しました。 フィールドに値を入力し、IDを3に設定しました。これは、GigiBuffonに属する永続データのIDに対応します。 update()を呼び出すと、別の永続コンテキストからエンティティをロードした場合とまったく同じ効果があります。
実際、セッションは、再接続されたエンティティがどこから発生したかを区別しません。
HTMLフォーム値からデタッチされたエンティティを構築することは、Webアプリケーションでは非常に一般的なシナリオです。
Session に関する限り、デタッチされたエンティティは、ID値が永続データに対応する単なるプレーンエンティティです。
上記の例はデモの目的にすぎないことに注意してください。 そして、私たちは自分たちが何をしているのかを正確に知る必要があります。 そうしないと、更新するフィールドに値を設定しただけで、残りの部分は変更されないまま(つまり、実質的にnull)、エンティティ全体でnull値になる可能性があります。
6. 一時的なエンティティ
一時エンティティは、永続ストアに表現がなく、セッションによって管理されていない単なるエンティティオブジェクトです。
一時的なエンティティの典型的な例は、コンストラクターを介して新しいエンティティをインスタンス化することです。
一時的なエンティティ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. 削除されたエンティティ
Session.delete(entity)が呼び出され、 Session がエンティティに削除のマークを付けた場合、エンティティは削除(削除)状態になります。 DELETEコマンド自体は、作業単位の最後に発行される場合があります。
次のコードでそれを見てみましょう:
session.delete(neymar);
assertThat(getManagedEntities(session).get(0).getStatus()).isEqualTo(Status.DELETED);
ただし、作業単位が終了するまで、エンティティは永続コンテキストストアにとどまることに注意してください。
8. 結論
永続コンテキストの概念は、Hibernateエンティティのライフサイクルを理解する上で中心的な役割を果たします。 各ステータスを示すコード例を調べることで、ライフサイクルを明確にしました。
いつものように、この記事で使用されているコードは、GitHubのにあります。