- 技術 💻
LazyInitializationExceptionの直し方|no Session
LazyInitializationException は、「まだ DB から読んでいない関連(遅延読み込みの関連)」を、DB とのつながり(Session)が閉じた後で読もうとしたときの例外です。 Spring では多くの場合、@Transactional のメソッドを抜けた後に、エンティティの getBooks() などを呼んだときに起きます。
文面は Hibernate の版で変わります。ソースで確かめた形は次のとおりです。
| Hibernate の版(Spring Boot の版) | コレクション(@OneToMany など)の文面 |
1件の関連(@ManyToOne など)の文面 |
|---|---|---|
| 5.6(Boot 2.x) | failed to lazily initialize a collection of role: 〈ロール〉, could not initialize proxy - no Session |
could not initialize proxy [〈エンティティ〉#〈ID〉] - no Session |
| 6.6(Boot 3.5) | failed to lazily initialize a collection of role: 〈ロール〉: could not initialize proxy - no Session |
Could not initialize proxy [〈エンティティ〉#〈ID〉] - no session |
| 7.2(Boot 4.0.8) | Cannot lazily initialize collection of role '〈ロール〉' (no session) |
Could not initialize proxy [〈エンティティ〉#〈ID〉] - no session |
| 7.4.5(Boot 4.1.1) | Cannot lazily initialize collection of role '〈ロール〉' with key '〈ID〉' (no session) |
Could not initialize proxy [〈エンティティ〉#〈ID〉] - no session |
〈ロール〉は com.example.demo.Author.books のように「エンティティのクラス名.フィールド名」です。どの文面でも、原因と直し方は同じです。
直し方は、「Session が開いている間に必要な関連を読み終える」ことです。 4つの方法の違いは次のとおりです。
| 直し方 | やること | 向いている場面 |
|---|---|---|
| JOIN FETCH | JPQL で関連を一緒に読む | 読む関連が決まっている検索 |
| @EntityGraph | リポジトリのメソッドに「一緒に読む関連」を付ける | クエリメソッドを書き換えずに済ませたい |
| DTO 射影 | 必要な列だけを DTO(受け渡し用のクラス)に詰める | 画面・API に返す一覧 |
サービス層の @Transactional(readOnly = true) |
トランザクションの中で関連を読み、DTO にしてから返す | 1つの処理で複数の関連を使う |
確認環境:Spring Boot 4.1.1(Hibernate ORM 7.4.5.Final)。例外の文面は Hibernate ORM の GitHub のタグ 5.6・6.6.58・7.0.10・7.1.36・7.2.25・7.4.5 のソース(AbstractPersistentCollection・AbstractLazyInitializer)を比べて確かめ、Spring Boot の各版が使う Hibernate の版は Boot の依存管理のファイルで確かめました。open-in-view の既定値と警告文は Spring Boot 4.1.1 のソース、enable_lazy_load_no_trans の説明は Hibernate 7.4.5 のソースの Javadoc に基づきます(確認日 2026年10月12日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「LazyInitializationExceptionの直し方|no Session」
なぜ起きるのか(遅延読み込みと Session)
遅延読み込みとは
JPA(Java のデータベース操作の標準仕様)では、関連の読み方を2種類から選べます。
| 読み方 | 動き | 既定になっている関連 |
|---|---|---|
FetchType.LAZY(遅延) |
最初は読まず、代わりの空のオブジェクト(プロキシ)を置く。触ったときに SQL を出す | @OneToMany・@ManyToMany |
FetchType.EAGER(即時) |
親と一緒に読む | @ManyToOne・@OneToOne |
遅延の関連は「触ったときに SQL を出す」ため、そのときに DB とのつながり(Session)が要ります。 Spring では、Session(JPA の EntityManager)は通常トランザクションと同じ長さで開いて閉じます。
起きる流れ
@Entity
public class Author {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "author") // 既定で LAZY
private List<Book> books = new ArrayList<>();
// getter 省略
}
@Entity
public class Book {
@Id @GeneratedValue
private Long id;
private String title;
@ManyToOne(fetch = FetchType.LAZY)
private Author author;
}
@Service
public class AuthorService {
private final AuthorRepository repository;
public AuthorService(AuthorRepository repository) { this.repository = repository; }
@Transactional(readOnly = true)
public Author find(long id) {
return repository.findById(id).orElseThrow();
} // ← ここでトランザクションが終わり、Session が閉じる
}
// 呼ぶ側(トランザクションの外)
Author author = authorService.find(1L);
author.getBooks().size(); // ← ここで LazyInitializationException
| 順番 | 起きること |
|---|---|
| 1 | find() に入るとトランザクションが始まり、Session が開く |
| 2 | Author だけを読む SQL が出る。books は空のプロキシのまま |
| 3 | find() を抜けるとトランザクションが終わり、Session が閉じる |
| 4 | Hibernate 7.4.5 のソースでは、Session を閉じるときにプロキシと Session のつながりを外す |
| 5 | getBooks().size() で SQL を出そうとするが、Session が無い → (no session) |
例外が出るのは getBooks() を呼んだ行ではなく、中身に触った行(size()・for 文・get(0) など)です。 getBooks() はプロキシを返すだけだからです。
open-in-view の既定と、いつ例外になるか
Spring Boot は、Web アプリでは既定で「リクエストの間ずっと Session を開いておく」設定(open-in-view)を有効にしています。 そのため、コントローラーや画面のテンプレートで遅延の関連を触っても、例外になりません。
Spring Boot 4.1.1 のソースでは、spring.jpa.open-in-view の既定値は true で、明示していないと起動時に次の警告が出ます。
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 warning
| 状況 | open-in-view: true(既定) | open-in-view: false |
|---|---|---|
| コントローラーで遅延の関連を触る | 例外なし(その場で SQL が出る) | 例外 |
| JSON 変換(Jackson)で遅延の関連を触る | 例外なし | 例外(Could not write JSON: … に包まれる) |
@Scheduled・@Async・バッチ |
例外(Web リクエストではない) | 例外 |
CommandLineRunner・メッセージの受信処理 |
例外 | 例外 |
テストのメソッドの中(@SpringBootTest) |
例外(Web リクエストではない) | 例外 |
「画面では動くのに、バッチやテストでは例外になる」のは、open-in-view が Web リクエストの中だけで効くためです。 逆に、open-in-view: false にしたとたん、画面でも例外が出始めます。
open-in-view を true のまま使うと、どこで SQL が出るかがコードから読みにくくなり、画面の描画中に大量の SQL(N+1 問題)が出ても気づきにくくなります。false にして、この記事の直し方で必要な関連を明示的に読む形にすると、SQL の数と場所がサービス層にまとまります。
spring:
jpa:
open-in-view: false
直し方1:JOIN FETCH で一緒に読む
JPQL(JPA のクエリ言語)の join fetch を使うと、親と関連を1回の SQL で読めます。 読んだ後は Session が閉じても使えます。
public interface AuthorRepository extends JpaRepository<Author, Long> {
@Query("select a from Author a join fetch a.books where a.id = :id")
Optional<Author> findWithBooks(@Param("id") long id);
// 本が0冊の著者も取りたいときは left join fetch
@Query("select a from Author a left join fetch a.books where a.name like :name")
List<Author> searchWithBooks(@Param("name") String name);
}
| 注意点 | 内容 |
|---|---|
ページング(Pageable)と一緒に使う |
コレクションの join fetch とページングを組み合わせると、Hibernate は全件を読んでからメモリ上で切り出す。ソースでは HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory の警告 |
List のコレクションを2つ以上 fetch |
MultipleBagFetchException: cannot simultaneously fetch multiple bags になる |
join だけで fetch を付け忘れる |
絞り込みに使うだけで、関連は読まれない(例外は残る) |
ページングが要る一覧は、直し方3の DTO 射影か、「ID だけをページングで取得 → その ID で join fetch」の2段階にします。
直し方2:@EntityGraph で一緒に読む
@EntityGraph は、リポジトリのメソッドに「この関連も一緒に読む」と書く方法です。 JPQL を書かずに済み、クエリメソッドにも付けられます。
public interface AuthorRepository extends JpaRepository<Author, Long> {
@EntityGraph(attributePaths = "books")
Optional<Author> findById(Long id); // 既存のメソッドを上書き
@EntityGraph(attributePaths = { "books", "books.reviews" })
List<Author> findByNameContaining(String name); // クエリメソッドにも付く
}
| JOIN FETCH | @EntityGraph | |
|---|---|---|
| 書く場所 | @Query の JPQL |
メソッドの注釈 |
クエリメソッド(findByName…)と使う |
JPQL に書き直す | そのまま付けられる |
| 複雑な条件 | 書きやすい | 条件は JPQL・メソッド名側 |
| 注意点 | 上の表と同じ | 上の表と同じ(中身は join の SQL になる) |
クエリメソッドの命名規則は、関連記事「Spring Data JPA使い方|クエリメソッド命名一覧と@Query実例」で扱っています。
直し方3:DTO 射影で必要な列だけ読む
画面や API に返すだけなら、エンティティではなく DTO を直接作るクエリにします。 DTO は普通の値なので、遅延読み込みが起きません。
// 受け渡し用の record
public record AuthorSummary(Long id, String name, long bookCount) {}
public interface AuthorRepository extends JpaRepository<Author, Long> {
@Query("""
select new com.example.demo.author.AuthorSummary(a.id, a.name, count(b))
from Author a left join a.books b
group by a.id, a.name
""")
Page<AuthorSummary> findSummaries(Pageable pageable);
}
| 良い点 | 注意点 |
|---|---|
| 必要な列だけを読むので SQL が軽い | 読んだ値を更新には使えない(エンティティではない) |
| ページングと組み合わせられる | new の後ろはパッケージを含めたクラス名 |
| JSON 変換で関連をたどらない | 画面ごとに DTO が増える |
コントローラーからエンティティをそのまま JSON で返していると、Jackson が全部の getter を呼ぶため、遅延の関連に触って例外になります。 DTO を返す形にすると、この例外と、意図しない列の公開を同時に防げます。
直し方4:サービス層の @Transactional(readOnly = true) の中で読み終える
トランザクションの中で必要な関連を読み、DTO に変換してから返します。 Session が開いている間に読み終えるので、外では例外になりません。
public record AuthorDetail(Long id, String name, List<String> bookTitles) {}
@Service
public class AuthorService {
private final AuthorRepository repository;
public AuthorService(AuthorRepository repository) { this.repository = repository; }
@Transactional(readOnly = true)
public AuthorDetail findDetail(long id) {
Author author = repository.findWithBooks(id).orElseThrow(); // 直し方1と組み合わせ
return new AuthorDetail(
author.getId(),
author.getName(),
author.getBooks().stream().map(Book::getTitle).toList());
} // ← DTO にしてから返すので、外で関連に触らない
}
| 書き方 | 結果 |
|---|---|
トランザクションの中で getBooks() に触る |
例外にならない(その場で SQL) |
| トランザクションの中で DTO にして返す | 外で例外にならない |
エンティティのまま返し、外で getBooks() |
例外 |
Hibernate.initialize(author.getBooks()) を中で呼ぶ |
例外にならない。ただし SQL は1回増える |
@Transactional だけで直すと、関連1つごとに SQL が1回増えます。 一覧で使うと N+1 問題になるため、直し方1・2と組み合わせます。N+1 問題の比較は関連記事「JPAのN+1問題の解決|JOIN FETCH等4手法の比較とコード全文」で扱っています。
@Transactional が付いているのに例外が出る場合は、同じクラスの中から呼んでいる・private メソッドに付けている、などでトランザクションが効いていない可能性があります。関連記事「Spring @Transactionalが効かない原因8選|ロールバックの直し方」で扱っています。
4つの直し方の比較
| JOIN FETCH | @EntityGraph | DTO 射影 | @Transactional で読み終える | |
|---|---|---|---|---|
| SQL の回数 | 1回 | 1回 | 1回 | 関連ごとに増える |
| ページング | コレクションは不向き | コレクションは不向き | 向いている | できる |
| 更新に使える | 使える | 使える | 使えない | 使える |
| 書く量 | JPQL | 注釈1つ | DTO と JPQL | DTO 変換 |
| 第一候補になる場面 | 詳細画面 | クエリメソッドの一覧 | API の一覧 | 処理の中で複数の関連を使う |
やってはいけない回避
| 回避のしかた | 起きる問題 |
|---|---|
hibernate.enable_lazy_load_no_trans: true |
触るたびに一時的な Session を開いて SQL を出す。Hibernate の Javadoc は「推奨しない」「トランザクションの分離を壊しやすい」と書いている |
関連を全部 FetchType.EAGER にする |
使わない画面でも毎回読む。一覧で大量の SQL が出る |
例外を try-catch で握りつぶす |
データが欠けたまま表示される |
open-in-view: true に頼り切る |
画面の描画中の SQL に気づきにくい。バッチやテストでは結局例外になる |
enable_lazy_load_no_trans について、Hibernate 7.4.5 のソースの説明は次のとおりです(設定の既定値は false)。
This behavior is not recommended since it can easily break transaction isolation or lead to data aliasing; it is therefore disabled by default.
# 使わない設定(例として載せるだけ)
spring:
jpa:
properties:
hibernate:
enable_lazy_load_no_trans: true
よくある質問
Q. テストのメソッドの中で関連に触ると例外になります。
A. @SpringBootTest のテストメソッドは、Web リクエストでもトランザクションの中でもないため、Session がありません。テストに @Transactional を付けるとテスト全体が1つのトランザクションになり例外は消えますが、本番では例外になるコードを見逃すことがあります。テストでも、サービスが DTO を返す形で確かめると本番と同じ条件になります。
Q. toString() やログ出力で例外になります。
A. Lombok の @Data・@ToString などで、toString() が遅延の関連もたどっていることがあります。関連のフィールドを toString() から外します(@ToString.Exclude など)。
Q. @ManyToOne の関連でも起きますか。
A. fetch = FetchType.LAZY にした @ManyToOne で起きます。文面は Could not initialize proxy [〈エンティティ〉#〈ID〉] - no session の形です。ID だけを使う getAuthor().getId() は、Hibernate では SQL を出さずに返せる場合があります。
Q. エラー文が the owning session was closed です。
A. Hibernate のソースでは、プロキシが Session を覚えたまま、その Session が閉じた場合の文です。no session と原因・直し方は同じです。
まとめ
- Session(トランザクション)が閉じた後に、遅延読み込みの関連に触ったときの例外
- 文面は Hibernate の版で変わる。Boot 4.1.1(Hibernate 7.4.5)は
Cannot lazily initialize collection of role '…' with key '…' (no session) - open-in-view(既定
true)は Web リクエストの中だけ。バッチ・テスト・@Asyncでは例外になる - 直し方は JOIN FETCH・@EntityGraph・DTO 射影・サービス層で読み終えて DTO で返す
enable_lazy_load_no_transと全部 EAGER は避ける
参考・出典
確認日はいずれも 2026年10月12日です。
- Hibernate ORM ソースコード(7.4.5)AbstractPersistentCollection:https://github.com/hibernate/hibernate-orm/blob/7.4.5/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java
- Hibernate ORM ソースコード(7.4.5)AbstractLazyInitializer:https://github.com/hibernate/hibernate-orm/blob/7.4.5/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java
- Hibernate ORM ソースコード(7.4.5)TransactionSettings(enable_lazy_load_no_trans):https://github.com/hibernate/hibernate-orm/blob/7.4.5/hibernate-core/src/main/java/org/hibernate/cfg/TransactionSettings.java
- Spring Boot ソースコード(v4.1.1)JpaBaseConfiguration:https://github.com/spring-projects/spring-boot/blob/v4.1.1/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/JpaBaseConfiguration.java
- Spring Boot リファレンス「SQL Databases」(Open EntityManager in View):https://docs.spring.io/spring-boot/reference/data/sql.html
- Spring Data JPA リファレンス「JPA Query Methods」:https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「LazyInitializationExceptionの直し方|no Session」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。