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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?