← Spring コース
55. JPA の永続化コンテキスト
(Jakarta Persistence)は、Java のと表の行を対応づける標準仕様です。ここでは実装の 7.4.5 と、メモリ内で動く DB の を使います。は、読んだ(行に対応するオブジェクト)を載せる作業机で、仕様は、同じ ID につきインスタンスが1つだけになる集まりと定義しています。机はの始まりに開かれ、終わると片付きました。
机は、何を読んだかと何が変わったかを覚えています。だから save を呼ばずにを書き換えただけで、を抜けた後に UPDATE が出ました。同じ ID をもう一度読んでも SQL は出ず、同じインスタンスが返りました。自分で UPDATE 文を書く方式とは違い、書き戻しは机が受け持ちます。Hibernate はこれを自動の(dirty checking)と呼んでいます。
つまずきやすいのは、机が片付いた後です。後から読みに行く設定()の関連に、片付いた後で触ると になりました。自分で囲まずに呼んだ findById も、が呼び出しごとに読み取り専用のトランザクションを始めて机を開き、戻る前に閉じます。また、UPDATE が出たのはコミットのときでしたが、クエリの前などに書き戻される場合もあります。
点線の付いた語はタップすると説明が出ます。用語集を開く →
@Transactional のメソッドに入る机が用意される
findById(1)
SELECT が1回出るAuthor が机に載る(managed=true)
もう一度 findById(1)SQL は出ない。同じインスタンスが返る
a.name を書き換える(save は呼ばない)
机の上の Author が変わるこの時点では SQL は出ていない
メソッドを抜ける
UPDATE が出てコミット変わった物だけ書き戻される
机が片付く
机が片付いた後の Authorまだ読んでいない関連に触ると LazyInitializationException
(no session)- ・自分で囲まずに findById を2回呼ぶと、リポジトリが呼び出しごとに読み取り専用のトランザクションを始め、そのたびに新しい机(EntityManager)を開いて閉じました。SELECT は2回出て、返るインスタンスも別々です
- ・Web アプリでは spring.jpa.open-in-view の既定が true で、リクエストの処理中は机が残ります。main スレッドからの呼び出しでは既定のままでも例外になりました
- ・机は複数のスレッドで共有してはならない、と Hibernate の入門ガイドにあります
サンプルコード(フレームワーク環境が必要なため表示のみ)
// JpaApp.java(抜粋)— Spring Boot 4.1.1 / Hibernate ORM 7.4.5.Final / H2、--spring.jpa.show-sql=true
@Service
class AuthorService {
private final AuthorRepository repo;
@PersistenceContext private EntityManager em;
AuthorService(AuthorRepository repo) { this.repo = repo; }
@Transactional
public void rename(Long id) {
Author a = repo.findById(id).orElseThrow();
System.out.println(" loaded, managed=" + em.contains(a));
a.name = "new name"; // フィールドを書き換えるだけ。save は呼ばない
System.out.println(" field changed, leaving the method");
}
}
// main から service.rename(id) を呼び、その後で読み直した結果
//
// --- 1. dirty checking: change a field, never call save()
// Hibernate: select a1_0.id,a1_0.name from author a1_0 where a1_0.id=?
// loaded, managed=true
// field changed, leaving the method
// Hibernate: update author set name=? where id=?
// Hibernate: select a1_0.id,a1_0.name from author a1_0 where a1_0.id=?
// after commit, name in DB = new name
図やコードに出てくる用語(タップで説明)
サンプルコードで身につける(5本) →
学んだ概念を、素朴な例から実務寄りの例まで5つの具体例で確認します。
公式ドキュメントで詳しく ↗