1エンティティの定義と、最初の保存で出る SQL
この回の実験で使う2つので、Author が Book を複数持ち、どちらの向きの関連も後から読みに行く設定()です。Author に Book を2つ足して save を1回呼ぶと、ID の採番と3件の insert が出ました。Book の insert まで出るのは books に cascade = CascadeType.ALL を付けているためで、SQL の文面は 7.4.5 と でのものです。
Hibernate ORM version = 7.4.5.Final--- 0. createHibernate: select next value for author_seqHibernate: select next value for book_seqHibernate: select next value for book_seqHibernate: insert into author (name,id) values (?,?)Hibernate: insert into book (author_id,title,id) values (?,?,?)Hibernate: insert into book (author_id,title,id) values (?,?,?)
@Entity
class Author {
@Id @GeneratedValue Long id;
String name;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL) // 既定で LAZY
List<Book> books = new ArrayList<>();
protected Author() {}
Author(String name) { this.name = name; }
}
@Entity
class Book {
@Id @GeneratedValue Long id;
String title;
@ManyToOne(fetch = FetchType.LAZY) Author author;
protected Book() {}
Book(String title, Author author) { this.title = title; this.author = author; }
}
interface AuthorRepository extends JpaRepository<Author, Long> {}
// AuthorService の中
// @Transactional
// public Long create() {
// Author a = new Author("old name");
// a.books.add(new Book("Book A", a));
// a.books.add(new Book("Book B", a));
// return repo.save(a).id;
// }
2同じ ID を2回読む: 1つのトランザクションで囲む場合と囲まない場合
同じ ID で findById を2回呼ぶと、 のの中では SELECT は1回だけで、a1 == a2 は true です。自分で囲まずに呼んだ3番では SELECT が2回出て、b1 == b2 は false でした。 4.1.1 の にはに @Transactional(readOnly = true) が付いており、DEBUG ログでは findById の呼び出しごとにが始まり、そのたびに別の が開かれて閉じられていました。
--- 2. findById twice inside one transactionHibernate: select a1_0.id,a1_0.name from author a1_0 where a1_0.id=?a1 == a2 : true--- 3. findById twice with no surrounding transactionHibernate: select a1_0.id,a1_0.name from author a1_0 where a1_0.id=?Hibernate: select a1_0.id,a1_0.name from author a1_0 where a1_0.id=?b1 == b2 : false
// AuthorService の中(1つのトランザクション)
@Transactional
public void findTwiceInOneTransaction(Long id) {
Author a1 = repo.findById(id).orElseThrow();
Author a2 = repo.findById(id).orElseThrow();
System.out.println(" a1 == a2 : " + (a1 == a2));
}
// main の中(自分では囲んでいない。findById が呼び出しごとに自分のトランザクションを始める)
// Author b1 = repo.findById(id).orElseThrow();
// Author b2 = repo.findById(id).orElseThrow();
// System.out.println(" b1 == b2 : " + (b1 == b2));
3遅延ロード: 机があるうちは SQL が出て、片付いた後は例外になる
Author の books は、触ったときに初めて読みに行くコレクションです(実クラスは PersistentBag)。@Transactional のメソッドの中で size() を呼ぶと、その時点で book への SELECT が出ました。5番の Author は囲まずに呼んだ findById が返したもので、読んだときの机はもう閉じられているため になり、メッセージ末尾の (no session) がそれを表しています。
--- 4. lazy collection inside a transactionHibernate: select a1_0.id,a1_0.name from author a1_0 where a1_0.id=?books collection class = org.hibernate.collection.spi.PersistentBagHibernate: select b1_0.author_id,b1_0.id,b1_0.title from book b1_0 where b1_0.author_id=?size = 2--- 5. lazy collection outside a transactionorg.hibernate.LazyInitializationExceptionCannot lazily initialize collection of role 'l22.jpa.Author.books' with key '1' (no session)
4Web アプリでは open-in-view の既定が効く
@Transactional の無いコントローラで a.books.size() を呼ぶ URL を叩いた結果です(--- で始まる行は区切りとして足したもの)。 の既定は true で、起動時に WARN が1行出て、リクエストはにならずにその場で SQL が出て 200 が返りました。false にして起動すると同じリクエストが 500 になり、WARN は出なくなります。
--- 既定のまま起動(起動時の WARN と、リクエスト時の SQL)WARN 1 --- [data] [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warningHibernate: select a1_0.id,a1_0.name from author a1_0Hibernate: select b1_0.author_id,b1_0.id,b1_0.title from book b1_0 where b1_0.author_id=?--- --spring.jpa.open-in-view=false で起動(応答とログ)HTTP/1.1 500Content-Type: application/jsonTransfer-Encoding: chunkedDate: Thu, 01 Oct 2026 13:34:29 GMTConnection: close{"timestamp":"2026-10-01T13:34:29.675Z","status":500,"error":"Internal Server Error","path":"/authors/first/book-count"}ERROR 1 --- [data] [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.hibernate.LazyInitializationException: Cannot lazily initialize collection of role 'l22.jpa.Author.books' with key '1' ...
5素の Java で: 作業机の模型
の考え方を Map でなぞった模型で、SQL: の行は模型が表示している文字列です(DB には接続していません)。ID から実物への対応表があるので2回目の find は DB へ行かず、読んだ時点の写しと比べることで save 無しの変更を見つけます。写しと比べる考え方は、拡張を使わない場合について Hibernate の入門ガイドが説明しているものと同じですが、Map の作りやコミット時にだけ比べる点は説明用で、Hibernate はクエリの前にも書き戻すことがあります。