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のAuto Configuration周辺のクラス図を描いてみた
  5. ModelMapperで1対1に対応しないフィールドのマッピング
  6. Javaでオブジェクト配列からフィールド配列を生成
  7. Javaサーブレットにおける部分URLやファイルパスの取得

comments powered by Disqus