0
0

JPAの永続性コンテキストとEntityManagerについてご説明いたします

Posted at

入る前に

  • 私は韓国人として、日本語の勉強とコンピュータの勉強を同時に行うために、ここに文章を書きます
  • 翻訳機の助けを借りて書かれた文章なので、誤りがあるかもしれません
  • この内容は基本的にJPAを知っていて、初めて入門する方に向けて作成されました

パーシステンスコンテキストとは? (Persistence Context)

image.png

永続コンテキストは、一意のエンティティインスタンスが任意の永続的なIDに対して存在するようなエンティティのセットです。永続コンテキスト内では、エンティティが管理されます。EntityManagerはそのライフサイクルを制御し、エンティティはデータストアリソースにアクセスできます。
永続コンテキストが終了すると、以前管理されていたエンティティはデタッチされます。デタッチされたエンティティはもはやEntityManagerの制御下にはなく、データストアリソースにアクセスできなくなります。デタッチについての詳細は「2. エンティティライフサイクル管理」セクションで説明しますが、今のところデタッチには2つの明白な結果があることを知っておけば十分です。

理解しにくく書かれているね。
これから一つずつ学んでいましょう。

まずは簡単に永続性コンテキストを目通しましょう。

  • 永続性コンテキストは、メモリ上に存在するエンティティオブジェクトの集まりと説明できます

  • 永続性コンテキストは、データベースから取得されたエンティティをキャッシュし、同じ永続性コンテキスト内で同一のエンティティを取得するときに、エンティティの状態変化を検知し、データベースと同期を取る役割を果たします

第一レベルキャッシュ (First-Level Cache)

永続性コンテキストは、第一レベルキャッシュとしても機能します。第一レベルキャッシュは、特定のトランザクション内でEntityManagerによって管理されるエンティティのメモリ上の保存領域です。EntityManagerが同じトランザクション内でエンティティを再度検索する際、データベースにアクセスする代わりに、第一レベルキャッシュに保存されているエンティティを返します。これにより、パフォーマンスが向上し、データベースへの不要なアクセスが削減されます。

まとめると、永続性コンテキストはメモリにエンティティをキャッシュしておくものであり、このキャッシュ機能が第一レベルキャッシュとして知られています。

永続性コンテキストとは (Persistence Context)
永続コンテキストは、一意のエンティティインスタンスが任意の永続的なIDに対して存在するようなエンティティのセットです。
永続コンテキスト内では、エンティティが管理されます。EntityManagerはそのライフサイクルを制御し、エンティティはデータストアリソースにアクセスできます。

JPAの永続性コンテキストを理解する前に、まず EntityManagerが何かを知る必要があります。


EntityManagerとは?

image.png

これはEntityManagerインターフェースだ。注釈を読んでみよう!

  • EntityManagerインスタンスは永続性コンテキストに関連付けられています。永続性コンテキストは、任意の永続的なエンティティ識別子に対して一意のエンティティインスタンスが存在するエンティティインスタンスのセットです。永続性コンテキスト内では、エンティティインスタンスとそのライフサイクルが管理されます
  • EntityManager APIは、永続的なエンティティインスタンスを作成および削除し、主キーでエンティティを検索し、エンティティに対してクエリを実行するために使用されます
  • 特定のEntityManagerインスタンスが管理できるエンティティのセットは、永続性ユニットによって定義されます。永続性ユニットは、アプリケーションによって関連付けられたりグループ化されたすべてのクラスのセットを定義し、それは単一のデータベースへのマッピングにおいて同じ場所に配置されなければなりません

EntityMangerに定義されたメソッドを詳しく見れば、コメントを理解することができます

  • CRUD操作の管理
    EntityMangerは、保存(persist)、検索(find)、修正(merge)、削除(remove)する機能を提供します
    image.png

image.png

今日はテーマを理解しやすいメソッドを持ってきました。
find()メソッドを見てみると、プライマリキーに基づいてエンティティを識別し、返します。
どのように見つけるのでしょうか?

例えば、Userというクラスがあるとします。
もし永続性コンテキストにidが1の該当するオブジェクトがあれば、そのオブジェクトを取得します。

image.png

もしuser id 1のオブジェクトが永続性コンテキストにない場合は?
DBにSELECTクエリを送って取得した後、User(id: 1, name: haroya)オブジェクトを生成し、永続性コンテキストに保存してから返します。
(簡単に説明するためにnewと表現しましたが、実際にはJPAは内部的にリフレクション(Reflection)を使用して、データベースから取得した値をUserオブジェクトのフィールドに設定します)

ここで新しい情報を知ることができます。
ああ!EntityManagerがそうなら、データベースに検索クエリを SELECT * FROM User WHERE id = 1; 送る役割をしますか?

はい、その通りです。これはEntityManagerのcreateQuery()メソッドでさらに詳しく指定されています。

  • JPQLクエリ実行

image.png

JPQLとは何ですか?
JPQLはJPAで使用するオブジェクト指向クエリ言語で、DBMS(リレーショナルデータベース)の文法ではなく、Javaオブジェクト(エンティティ)を対象にクエリを作成できます。これによりデータベースに依存せず、さまざまなDBMSで一貫した方法でクエリを作成することができます。結果として、JPQLはオブジェクト-リレーショナルインピーダンスミスマッチを緩和し、コードの柔軟性と保守性を向上させるのに寄与します。

  • 自動変更検知 (Dirty checking)
    image.png

EntityManager.persist()を使用すると、エンティティが永続性コンテキストに含まれた後、JPAはそのエンティティの状態を追跡します。

以降のコードは、円滑な説明のためにトランザクションとcommit()を省略しました。

EntityManager em = entityManagerFactory.createEntityManager();

User user = new User();
user.setId(1);
user.setName("haroya");

em.persist(user);

user.setName("kim");

em.flush();

簡単なコードを書きました。EntityManager.persist()を使用すると、そのエンティティは永続状態になります。

image.png

ユーザーオブジェクトの名前をkimに変更すると、以下の図のようになります。

  1. flush()が呼び出されると、永続性コンテキストは現在管理中のエンティティの状態をデータベースに同期します。この時、永続性コンテキストはエンティティの現在の状態とデータベースに保存された状態を比較し、変更された部分を検出します。例えば、user(1, haroya)という初期状態とuser(1, kim)という現在の状態を比較して、name属性が変更されたことを感知します。
  2. 変更事項が検知されると、JPAは自動的に必要なSQL UPDATEクエリを生成します
  3. 生成されたUPDATEクエリがデータベースに送信され、実際にデータベースに適用されます。この過程で、永続性コンテキストに保管されていた変更事項がデータベースと同期されます

再確認しよう! 一次キャッシュ:エンティティのスナップショットストレージ

  • 永続性コンテキストはEntityManagerが管理するメモリ内のエンティティオブジェクトを指し、このコンテキスト内にあるエンティティは一次キャッシュに保存されます
  • スナップショットはエンティティの状態をメモリに保存し、変更点を追跡してデータベースと同期するために使用されます。このスナップショットがまさに一次キャッシュで管理されるエンティティの状態です

簡単に言えば、一次キャッシュはEntityManagerが管理するメモリ内のストレージで、永続性コンテキストでエンティティのスナップショットを保管し、同じトランザクション内で効率的に管理します。

flush()の追加説明

  • flush()メソッドは永続性コンテキストの変更をデータベースに反映しますが、この作業はトランザクション内で実行される必要があります。トランザクションがコミットされるまで、これらの変更が実際に永続的に保存されることはありません
  • もしトランザクションなしでflush()が呼び出された場合、データベースに反映されないか、例外が発生する可能性があります。したがって、flush()は必ずトランザクション内で呼び出す必要があります

image.png

flush()を呼び出すと、永続性コンテキスト(Persistence Context)に保管されたエンティティの変更事項がデータベースと同期されます。
つまり、これまでの変更、追加、削除の内容がすべてDBに反映されるということです。

なぜ書き込み遅延ストレージを使用するのか?

  • 性能向上: 複数回のデータベースアクセスを最小限に抑えることで、性能を向上させます。変更事項を一度に処理するため、データベースとの通信回数が減少します
  • トランザクション管理の容易さ: トランザクションがコミットされる前にはデータベースに反映されないため、トランザクションのロールバック時に状態を簡単に戻すことができます

永続エンティティの同一性保証
同一性保証とは、同じ永続性コンテキスト内で同じ識別子(ID)を持つエンティティは、常に同じJavaオブジェクト(インスタンス)として維持されることを意味します。つまり、EntityManagerが管理する間、同じIDを持つエンティティは常に同じインスタンスとして返されます。
例えば、永続性コンテキスト内でUserエンティティをIDで検索する際、そのIDに該当するエンティティオブジェクトがすでに永続性コンテキストに存在している場合、新しいオブジェクトを生成する代わりに既存のオブジェクトが返されます。これにより、エンティティの同一性が保証されます。

EntityManager em = entityManagerFactory.createEntityManager();

User user1 = em.find(User.class, 1L);
User user2 = em.find(User.class, 1L);

System.out.println(user1 == user2); // true

変更検知は、永続性コンテキストが管理する永続状態のエンティティにのみ適用されま

以下のコードとコメントで続けて説明します。

// EntityManager crreated
EntityManager em = entityManagerFactory.createEntityManager();

// 1. 新しい永続状態のエンティティを作成して追加
User managedUser = new User();
managedUser.setId(1L);
managedUser.setName("haroya");
em.persist(managedUser);  // 永続状態にする

// 2. 永続状態のエンティティのプロパティを変更
managedUser.setName("kim");  // 変更が検知される

// 3. flushを呼び出して変更内容をデータベースに同期
em.flush();  // トランザクションなしで変更内容をデータベースに反映

// 4. 新しい非永続状態のエンティティを作成
User detachedUser = new User();
detachedUser.setId(1L);
detachedUser.setName("haroya");

// 5. 非永続状態のエンティティのプロパティを変更
detachedUser.setName("kim");  // 変更は検知されない

// 6. 非永続状態のエンティティはflushで変更を反映できない
em.flush();  // 何も起こらない

// EntityManagerを閉じる
em.close();

  1. 新しい永続状態のエンティティを作成して追加
    image.png

  2. 永続状態のエンティティのプロパティを変更
    image.png

  3. flushを呼び出して変更内容をデータベースに同期
    image.png
    image.png

  4. 新しい非永続状態のエンティティを作成
    image.png

  5. 非永続状態のエンティティのプロパティを変更
    image.png

  6. 非永続状態のエンティティはflushで変更を反映できない
    image.png

  7. EntityManagerを閉じる
    image.png

  • トランザクション管理
EntityManager em = entityManagerFactory.createEntityManager();

try {
    em.getTransaction().begin();
    
    User managedUser = em.find(User.class, 1L);
    managedUser.setName("kim");

    throw new RuntimeException("予期せぬエラー");

    em.getTransaction().commit();
} catch (Exception e) {
    em.getTransaction().rollback();
} finally {
    em.close();  
}

もしコード実行中に予期せぬ箇所でエラーが発生した場合、その時点までのすべてのデータベース作業はトランザクションによって保護されているため、実際にはデータベースに反映されず、トランザクションがロールバックされます。

つまり、エラー発生前に行われたすべての変更が取り消され、データベースはトランザクション開始前の状態に復元されます。


EntityManager整理

  • CRUD作業管理
    EntityManagerはエンティティの作成、検索、修正、削除の作業を管理し、これを通じてデータベースとの基本的な相互作用を行います

  • JPQLクエリ実行
    EntityManagerはオブジェクト指向のクエリ言語であるJPQLを使用して、データベースと独立してエンティティを対象としたクエリを作成および実行できます

  • 自動変更検知(Dirty checking)
    EntityManagerは永続性コンテキストで管理されるエンティティの状態変化を自動的に検知し、トランザクションがコミットされるときに変更内容をデータベースに反映します。

  • 永続性コンテキスト管理
    EntityManagerは永続性コンテキストを通じてエンティティのライフサイクルを管理し、同じトランザクション内でエンティティの一貫性を維持します

  • トランザクション管理
    EntityManagerはトランザクションの開始、コミット、ロールバックを制御し、データベース操作を一貫して処理できるように支援します

エンティティのライフサイクルと状態 (EntityのLife cycle)
エンティティには、非永続状態 (Transient)、永続状態 (Persistent)、準永続状態 (Detached) の3つの主な状態があります

  • 非永続状態 (Transient): データベースに保存されておらず、永続性コンテキストにも関連付けられていないエンティティです

  • 永続状態 (Persistent): 永続性コンテキストによって管理され、データベースに保存されているエンティティです。状態の変更は自動的に検知され、データベースに反映されます

  • 準永続状態 (Detached): 一度永続状態だったが、永続性コンテキストから切り離されたエンティティです。この状態ではエンティティの変更はデータベースに反映されません


お得な豆知識 EntityManagerFactoryとは?

image.png

EntityManagerFactoryとは?

EntityManagerFactoryはJPA(Java Persistence API)で永続ユニットを基にEntityManagerを生成し管理するインターフェースで、このインターフェースを実装したオブジェクトがデータベースとの相互作用を処理します。

EntityManagerFactoryの重要な部分をチェックしよう

EntityManagerFactoryは生成コストが非常に大きいです

EntityManagerFactoryは、JPAアプリケーションでデータベース接続と永続ユニットに関する設定を管理する高コストのオブジェクトです。このオブジェクトは、アプリケーションが起動する際に一度生成され、複数のEntityManagerを生成するために使用されます。

 * Interface used to interact with the entity manager factory
 * for the persistence unit.
 *
 * <p>When the application has finished using the entity manager
 * factory, and/or at application shutdown, the application should
 * close the entity manager factory.  Once an
 * <code>EntityManagerFactory</code> has been closed, all its entity managers
 * are considered to be in the closed state.
 *
 * @since 1.0
 */
public interface EntityManagerFactory extends AutoCloseable 

アプリケーションがEntityManagerFactoryの使用を完了したとき、またはアプリケーションが終了する際に、EntityManagerFactoryを必ず閉じるべきだとコメントに書かれています。

  • このため、EntityManagerFactoryを生成した後、必要なときに
EntityManager em = emf.createEntityManager();

を呼び出して、EntityManagerを生成するのが一般的です。

これをどのように設定するかは、私のリポジトリを一度参考にしてみてください。
github_myrepo

EntityManagerFactoryと二次キャッシュ

image.png
以前、永続性コンテキストを勉強しながら一次キャッシュを調べました。
これは先ほどの例のコードです。
(userオブジェクトが永続状態であると仮定しよう。)

この場合、それぞれ異なるEntityManagerを使用すると、共有されないという問題があります。

image.png

このような問題を解決するために、JPAでは 二次キャッシュ(Second-Level Cache) を使用します。二次キャッシュはEntityManagerFactory単位で管理され、複数のEntityManager間でエンティティを共有できるようにします。

image.png

では、EntityManager1とEntityManager2で同時にアクセスしてuser(id: 1)を修正したらどうなると思いますか?
image.png

  • このような状況では、最初にコミットされたEntityManagerの変更が、後でコミットされたEntityManagerの変更によって上書きされる可能性があるため、同時性の問題が発生する可能性があり、これを防ぐために一般的にロッキングメカニズムが必要です

データ整合性の問題が発生します。このような問題を解決するために、一般的に 楽観的ロック(Optimistic Locking), や悲観的ロック(Pessimistic Locking) などの並行性制御手法を使用します。

  • 楽観的ロック: エンティティのバージョン情報を利用して、コミット時にデータベースのエンティティバージョンと現在のトランザクションのエンティティバージョンを比較します。もしバージョンが異なる場合、衝突が発生したとみなし、例外を投げます
    @Entity
    @Getter 
    @Setter
    public class User {
        @Id
        private Long id;
    
        @Version
        private Integer version;
    
        private String name;
        
    }
    
  • 悲観的ロック: トランザクション内でエンティティに対してロックをかけ、他のトランザクションがエンティティを修正できないようにします。これは衝突を未然に防ぐ方法です
    User user = em.find(User.class, 1L, LockModeType.PESSIMISTIC_WRITE);
    

上記の点を通じて、EntityManagerの並行性の問題を理解し、注意して使用する必要があります。

この部分に関する詳細な説明は、次回の投稿で行います。


締めくくりに

いかがでしたか。
JPAの永続性コンテキストやEntityManagerなどの理解に役立ちましたか?
書いているうちに追加すべき内容が多くなり、何度かに分けて投稿しなければならないと思います。
次回、もう少しディテールを細かく整理して戻ってきます。


リファレンス

Java ORM標準JPAプログラミング(자바 orm 표준 jpa 프로그래밍)
openjpa.apache

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0