1. 概要

Hibernateのような永続性プロバイダーは、永続性コンテキストを利用して、アプリケーションのエンティティライフサイクルを管理します。

このチュートリアルでは、永続コンテキストの紹介から始め、次にそれが重要である理由を説明します。 最後に、トランザクションスコープの永続コンテキストと拡張スコープの永続コンテキストの違いを例を挙げて説明します。

2. 永続コンテキスト

永続コンテキストの公式定義を見てみましょう。

EntityManagerインスタンスは永続コンテキストに関連付けられています。 永続コンテキストは、永続エンティティIDに対して一意のエンティティインスタンスが存在するエンティティインスタンスのセットです。 永続性コンテキスト内で、エンティティインスタンスとそのライフサイクルが管理されます。 EntityManager APIは、永続エンティティインスタンスの作成と削除、主キーによるエンティティの検索、およびエンティティのクエリに使用されます。

上記のステートメントは、現時点では少し複雑に見えるかもしれませんが、先に進むにつれて完全に理にかなっています。 永続コンテキストは、すべてのエンティティがデータベースからフェッチされるか、データベースに保存される第1レベルのキャッシュです。 これは、アプリケーションと永続ストレージの間にあります。

永続コンテキストは、管理対象エンティティに加えられた変更を追跡します。 トランザクション中に何かが変更された場合、エンティティはダーティとしてマークされます。 トランザクションが完了すると、これらの変更は永続ストレージにフラッシュされます。

EntityManager は、永続コンテキストと対話できるようにするインターフェイスです。 EntityManager を使用するときはいつでも、実際には永続コンテキストと対話しています。

エンティティで行われたすべての変更が永続ストレージを呼び出す場合、何回の呼び出しが行われるかを想像できます。 永続的なストレージ呼び出しはコストがかかるため、これはパフォーマンスに影響を与えます。

3. 永続コンテキストタイプ

永続性コンテキストには、次の2つのタイプがあります。

  • トランザクションスコープの永続コンテキスト
  • 拡張スコープの永続コンテキスト

それぞれを見てみましょう。

3.1. トランザクションスコープの永続コンテキスト

トランザクション永続コンテキストはトランザクションにバインドされます。 トランザクションが終了するとすぐに、永続コンテキストに存在するエンティティが永続ストレージにフラッシュされます。

トランザクション内で操作を実行すると、 EntityManager 永続コンテキストをチェックします存在する場合は、それが使用されます。 それ以外の場合は、永続コンテキストが作成されます。

デフォルトの永続コンテキストタイプはPersistenceContextType.TRANSACTIONです。EntityManager にトランザクション永続コンテキストを使用するように指示するには、@PersistenceContextで注釈を付けます。

@PersistenceContext
private EntityManager entityManager;

3.2. 拡張スコープの永続コンテキスト

拡張永続コンテキストは、複数のトランザクションにまたがることができます。 トランザクションなしでエンティティを永続化することはできますが、トランザクションなしでエンティティをフラッシュすることはできません。

EntityManager に拡張スコープの永続コンテキストを使用するように指示するには、@PersistenceContexttype属性を適用する必要があります。

@PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager entityManager;

ステートレスセッションBeanでは、 あるコンポーネントの拡張永続コンテキストは、別のコンポーネントの永続コンテキストを完全に認識していませんこれは、両方が同じトランザクションにある場合でも当てはまります。

トランザクションで実行されているコンポーネントAのメソッドにエンティティを永続化するとします。 次に、コンポーネントBのメソッドを呼び出します。 Component Bのメソッド永続化コンテキストでは、Component Aのメソッドで以前に永続化したエンティティが見つかりません。

4. 永続コンテキストの例

永続コンテキストについて十分に理解できたので、次に例を見てみましょう。 トランザクション永続コンテキストと拡張永続コンテキストを使用して、さまざまなユースケースを作成します。

まず、サービスクラスTransactionPersistenceContextUserServiceを作成しましょう。

@Component
public class TransctionPersistenceContextUserService {

    @PersistenceContext
    private EntityManager entityManager;
    
    @Transactional
    public User insertWithTransaction(User user) {
        entityManager.persist(user);
        return user;
    }
    
    public User insertWithoutTransaction(User user) {
        entityManager.persist(user);
        return user;
    }
    
    public User find(long id) {
        return entityManager.find(User.class, id);
    }
}

次のクラスExtendedPersistenceContextUserServiceは、 @PersistenceContext アノテーションを除いて、上記と非常によく似ています。 今回は、PersistenceContextType.EXTENDED@PersistenceContextアノテーションのtypeパラメーターに渡します。

@Component
public class ExtendedPersistenceContextUserService {

    @PersistenceContext(type = PersistenceContextType.EXTENDED)
    private EntityManager entityManager;

    // Remaining code same as above
}

5. テストケース

サービスクラスを設定したので、トランザクション永続コンテキストと拡張永続コンテキストを使用してさまざまなユースケースを作成します。

5.1. トランザクションの永続性コンテキストのテスト

トランザクションスコープの永続コンテキストを使用して、Userエンティティを永続化しましょう。 エンティティは永続ストレージに保存されます。 次に、拡張永続コンテキストの EntityManager を使用してfind呼び出しを行うことにより、検証します。

User user = new User(121L, "Devender", "admin");
transctionPersistenceContext.insertWithTransaction(user);

User userFromTransctionPersistenceContext = transctionPersistenceContext
  .find(user.getId());
assertNotNull(userFromTransctionPersistenceContext);

User userFromExtendedPersistenceContext = extendedPersistenceContext
  .find(user.getId());
assertNotNull(userFromExtendedPersistenceContext);

トランザクションなしでUserエンティティを挿入しようとすると、TransactionRequiredExceptionがスローされます。

@Test(expected = TransactionRequiredException.class)
public void testThatUserSaveWithoutTransactionThrowException() {
    User user = new User(122L, "Devender", "admin");
    transctionPersistenceContext.insertWithoutTransaction(user);
}

5.2. 拡張永続コンテキストのテスト

次に、拡張された永続コンテキストを使用して、トランザクションなしでユーザーを永続化しましょう。 User エンティティは永続コンテキスト(キャッシュ)に保存されますが、永続ストレージには保存されません。

User user = new User(123L, "Devender", "admin");
extendedPersistenceContext.insertWithoutTransaction(user);

User userFromExtendedPersistenceContext = extendedPersistenceContext
  .find(user.getId());
assertNotNull(userFromExtendedPersistenceContext);

User userFromTransctionPersistenceContext = transctionPersistenceContext
  .find(user.getId());
assertNull(userFromTransctionPersistenceContext);

永続エンティティIDの永続コンテキストでは、一意のエンティティインスタンスが存在します。 同じ識別子を持つ別のエンティティを永続化しようとすると、次のようになります。

@Test(expected = EntityExistsException.class)
public void testThatPersistUserWithSameIdentifierThrowException() {
    User user1 = new User(126L, "Devender", "admin");
    User user2 = new User(126L, "Devender", "admin");
    extendedPersistenceContext.insertWithoutTransaction(user1);
    extendedPersistenceContext.insertWithoutTransaction(user2);
}

EntityExistsException が表示されます:

javax.persistence.EntityExistsException: 
A different object with the same identifier value
was already associated with the session

トランザクション内の拡張永続コンテキストは、トランザクションの終了時にエンティティを永続ストレージに保存します。

User user = new User(127L, "Devender", "admin");
extendedPersistenceContext.insertWithTransaction(user);

User userFromDB = transctionPersistenceContext.find(user.getId());
assertNotNull(userFromDB);

拡張永続コンテキストは、トランザクション内で使用されると、キャッシュされたエンティティを永続ストレージに流し込みます。 まず、トランザクションなしでエンティティを永続化します。 次に、トランザクションで別のエンティティを永続化します。

User user1 = new User(124L, "Devender", "admin");
extendedPersistenceContext.insertWithoutTransaction(user1);

User user2 = new User(125L, "Devender", "admin");
extendedPersistenceContext.insertWithTransaction(user2);

User user1FromTransctionPersistenceContext = transctionPersistenceContext
  .find(user1.getId());
assertNotNull(user1FromTransctionPersistenceContext);

User user2FromTransctionPersistenceContext = transctionPersistenceContext
  .find(user2.getId());
assertNotNull(user2FromTransctionPersistenceContext);

6. 結論

このチュートリアルでは、永続性のコンテキストについて十分に理解しました。

最初に、トランザクションの存続期間を通じて存在するトランザクションの永続性コンテキストを調べました。 次に、複数のトランザクションにまたがることができる拡張永続コンテキストを確認しました。

いつものように、サンプルコードはGitHubから入手できます。