Spring Data JPAのgetOne()で遅延ロードエラー
Spring Data JPA + Hibernateで、RepositoryのgetOne(id)を呼ぶと以下のExceptionが発生しました。(エンティティ名は伏字にしています)
Caused by: org.hibernate.LazyInitializationException: could not initialize proxy [*****] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:155) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:268) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:73) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
代わりにfindById(id)を呼んでみると、Exceptionが発生しなくなりました。
そこでgetOneメソッドとfindOneメソッドの違いと、このExceptionの原因について調査しました。
原因と対策
結論から言うと、トランザクション関係の問題でした。
メソッドを呼び出しているのは@Serviceクラスは試作段階だったのでまだ@Transactionalを付けていなかったのですが、付けたら発生しなくなりました。
JpaRepositoryのJavadocによればgetOneはReferenceを返しますが、遅延読み込みとトランザクション境界は密接に関係するので、このような現象になっていたようです。
なお、Entityクラスに@Proxy(lazy=false)
を付ければ解決するという情報も多くありました。
しかし遅延読み込みしたいときには使えませんし、あまり本質的な解決になっていないと思ったので、こちらは採用しませんでした。
getOneメソッドとfindByIdメソッドの違い
findByIdはCrudRepositoryのメソッドとして、getOneはJpaRepositoryのメソッドとしてそれぞれ定義されています。
結局どちらを使ったほうが良いのかというお話ですが、以下の観点からfindById()を使ったほうがよさそうです。
- MongoDBなどに変更しても、findByIdメソッドがそのまま使用可能(MongoRepositoryはCrudRepositoryを継承)
- findByIdの戻り値はOptional
なので扱いやすい(getOneの戻り値はEntityそのもの)
まとめ
というわけで、トランザクションと遅延読み込みの問題でした。
特段の理由がなければ、getOneよりもfindByIdを使ったほうが良さそうです。
getOneの代わりにfindByIdを使えば解決するお話ですが、そもそも@Serviceクラスに@Transactionalを付け忘れている可能性があるので、現象が起きたときは注意が必要です。