JPA の Detached な Entity と Lazy Loading

今日ツイッターやら JJUG やらで id:megascus さんと JPA の話をちらっとして、なんか懐かしくなったので JPA ネタでも書いてみる。 JPA 1.0 の頃の知識なのでもしかすると古いかもしれない。

JPA を使う場合、トランザクション外で Lazy Loading を行うと例外がすっ飛ぶのは有名な話。だから例えば EntityManager でエンティティ引っ張ってきてそのまま JSP とかで lazy で関連たどって表示しようとすると死ぬ。

このへんはプロバイダー毎に動作が異なるらしいけど、少なくとも Hibernate や Eclipselink では例外になる模様*1。素直に null とか空リストを返してくれればいいものを、なんでわざわざそんなことしてくれるのか知らんが、そういう動きになってるからには例外にならないようアプリケーション側で対応しなければならない。

自分の中でもこれぞというのは無いが、対応方法自体はいくつかあるので知ってるものをざっと挙げてみる。変なとこがあれば突っ込んでいただけると幸い。

Open Session in View パターン

トランザクション外なのがだめならビューをトランザクションに含めればいいじゃない!という漢らしい解決方法。例えばサーブレットフィルターあたりでトランザクションの開始・終了をやったりする。 JPA 以前の Hibernate なんかで流行ったパターンなのかな?

これで確かに例外にならないので便利なんだけど、以下の様な問題がある。

  • ビューで DB 接続エラーなんかが起きるとエラーハンドリングしづらい
  • 一つの処理でトランザクションを複数に分けたい場合にめんどくさい
  • そもそもトランザクション境界はアプリケーションレイヤーで管理するべきではないのか

リクエストの開始と終了で強制的にトランザクション開始・終了になるのはちと無理があるとは思う。そもそも http のリクエストと DB のトランザクションは本質的に全然関係ないしな。

DTO パターン

エンティティとは別に、画面表示用のクラス(いわゆる DTO )を作ってトランザクション内で詰め替える作戦。

画面に必要な情報とエンティティの構造って必ずしも一致しないので、エンティティはドメインロジック、 DTO は画面向けロジック、と役割分担が出来る。エンティティに isHogeButtonEnabled() みたいなメソッドあるとげんなりするよね。そういう点ではアプリケーションがきれいになると思う。ファットだけど。

ただ、これはこれで以下のような問題がある。

  • 詰め替え作業がめんどい
  • DTO が爆発的に増える
  • エンティティの構造が変わると DTO の修正で死ぬ

DTO をどこまで共通化できるかってのも結構難しい判断が必要。無理に共通化すると、どこの画面用だかわからん処理が DTO 内に現れることになる。

Eager fetch

そもそも lazy loading は関連がまだ読み込まれてないから起きるのであって、あらかじめフェッチしといて lazy loading を起こさせないという解決方法。アノテーションで指定するか、 JPQL でもできる。 JPQL でのやり方は以下の記事を参照。

フィールド毎に lazy と eager を別々に設定できないとかで eager をやめた記憶がぼんやりとあるんだけど、そういうものだっけ?いまいち記憶があやふや。

あと eager で関連をどこまで辿れるんだったか・・孫とかひ孫まで eager で引っ張ってこれるのかな。どこかで結局 lazy が必要になったりするんだったっけ。このへんよく知らない。すみません。

なお、 .NET の Entity Framework だと Include 指定で孫でもひ孫でも引っ張ってこれる。 .NETLinq があるお陰でクエリを書くのが苦にならないのが素晴らしい。それに比べて JPA2 のクライテリアは・・。


昔は DTO パターン使ったけど、結局 Eager が一番まっとうなのかなぁ。

あー、 Java 開発したい。


追記( Open Session in View について)

@making さんより、 DB のトランザクションと Session(EntityManager) のライフサイクルは別物、という指摘を頂いた。そもそも Open Session in View とは名前の通り http のリクエストと DB の接続( Hibernate の Session など)のライフサイクルをあわせるものらしく、必ずしもそれとトランザクションを一致させるものではないということかな。

普段 EntityManager とトランザクションの開始・終了は基本的に同じ境界に設定するので、ごっちゃになってました。ご指摘ありがとうございました。

ただ JPA 以前の Hibernate では Session が open していれば lazy loading が動く*2のに対し、 JPA では Session(EntityManager) の open に加え、トランザクションが開始している必要がある。従って JPA 環境で Open Session in View をやるには結局 EntityManager のライフサイクルとトランザクションを合わせる必要があるんじゃないかなと思う。

なのでここでいう Open Session in View は本来の意味とは少し違う感じになるのかな。 Open Transaction in View とか?

*1:Toplink だと例外にならないらしい。http://rails.takeda-soft.jp/blog/show/134

*2:たぶん・・すいません、このへんよく知らない