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を付け忘れている可能性があるので、現象が起きたときは注意が必要です。

参考

関連記事


  1. Spring Data JPAのEntityクラスからDDLを生成する
  2. Open Session in Viewは使用すべきなのか?
  3. Spring Boot アノテーション集
  4. Spring Web MVCのViewでExcelを生成して返す
  5. Spring Web MVCのViewでPDFを生成して返す
  6. Spring Web MVCのAuto Configuration周辺のクラス図を描いてみた
  7. ModelMapperで1対1に対応しないフィールドのマッピング

comments powered by Disqus