Spring/Spring Bootと、JPA(Hibernate)を使っているプロジェクトにおいて、Lazy Fetchを指定しているParent → Childrenエンティティへのアクセスにおいて、以下の
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: xxx.yyy.Parent.children, could not initialize proxy - no Session
エラーが起きた時の原因と対処を簡単にメモしておきます。
状況と原因
LazyFetchが指定されているので、関連エンティティ(chidren)の取得を行ったタイミングでDBへのアクセスが発生します。で、その際にトランザクションが無効になっていて、エラーとなっています。
まぁ、エラーメッセージのまんまですね…。
状況的には Controller - Service1 - Service2 - Repository の構成をとっていて、 Service1 のメソッドに @Async
をつけており、非同期で実行することを想定してます。
要所だけを抜粋したコードにて、比較用に同期バージョンと非同期バージョンの両方を並べて記載します。
この例だと非同期バージョンのみ、当該エラーが起きました。
Controller
private final Service1 service1 @GetMapping("/sync") public String fooSync() { return service1.exec(); } @GetMapping("/async") public String fooAsync() throws Exception { CompletableFuture<String> future = service1.execAsync(); CompletableFuture.allOf(future).join(); // 結果を待つ(この例だと、非同期の意味がなくなるが…) return future.get(); }
Service1
private final Service2 service2; public String exec() { Parent parent = service2.query(); // parent.getChildren() return parent.toString(); } @Async public CompletableFuture<String> execAsync() { Parent parent = service2.query(); // parent.getChildren() return CompletableFuture.completedFuture(parent.toString()); }
Service2
private ParentRepository repository; public Parent query() { return repository.findById("123"); }
対処(ダメな例)
- Service2側のクラス/メソッドに
@Transactional
をつける - Service2側のクラス/メソッドに
@Transactional(propagation = Propagation.REQUIRES_NEW)
をつける
改めて書いてみると当たり前な感じですが、LazyFetchが呼ばれるのはService1側なので、Service2でトランザクションをつけても意味がありません。
対処
以下のどちらかの対処をすればOKです。後者についてはロジックの修正になりますね。
- Service1側のクラス/メソッドに
@Transactional
をつける - Service2側のメソッド内部でLazy Fetchが行われるよう、関連エンティティの取得もやっておく。
ちなみに、実務で遭遇したのはしょうもないオチだったのですが、 @Async
をつけたメソッド内でparallelストリームを使っていたりなんかすると、これまたやはりストリーム内の各処理(スレッド)でそれぞれトランザクションが有効になってないとならないです。
(非同期、並列処理、JPA、このあたりを絡んでくるとハマりどころが多いので、設計・技術選定は定期的に見直さないとなぁ。。)
参考サイト
Spring LazyInitializationException: no Session - Qiita
Loading Lazy loaded Entity in Async Thread in Spring Boot | by Prasim Jain | Medium