Re: hibernateを利用してはいけない5つのシチュエーション

なんか,Mapping-Framework スレまでヲチスレになってるみたいなんですが...
語句長^h^h^h獄長っておいらですか? エビジョンイルもおいらですか? 勘弁してぇ〜.
でもでも,マキアージュごっこは楽しそう.大人げない (笑).
そうですか,俺様ウットリですか.てへっ.読んだ瞬間,思わずにやけちゃいましたよ.フォーッ (違).
でもね,エビちゃんの画像は外せないね.なんてったって,この日記のメインコンテンツですから!! 残念!!!!
あ,クリテリアがいる♪


ともあれ (JW),今日は

シチュエーション1 表示層(ビュー)がHTMLでない場合


表示層に当たる部分がWebサービスであったり、Flash(Flex)と連携をする場合には、使用をしない方がよいでしょう。
基礎知識でふれたLazyローディングが悪さをします。
Lazyローディング自体は悪くないのですが、hibernateはデータベースを扱うコンポーネントであるが故の問題を抱えています。
それはある接続セッションで取得したオブジェクトに関連するオブジェクトの取得は、同一接続セッションを通して行わなければいけないという物です(違反をすると例外が発生します)。

について.
ここでは遅延ロードの問題を取り上げています.あるいは,Hibernate セッションのスコープの問題とも言えます.
これを読む限り,この著者さんはコントローラ (サービス層とかドメイン層とかロジック層とか適当に読み替えて) で Hibernate セッションおよびトランザクションを開始・終了しているようです.
そして取得した永続オブジェクトを Hibernate セッションを超えて,プレゼンテーション層でも使おうとしているようです.
これにより遅延ロードできずに例外が吹っ飛んでくることを問題にしているわけですね.


この問題の解決策は,基本的に次のいずれかになるんじゃないかと思ったりします.

  • Hibernate セッションのスコープをプレゼンテーション層まで広げる.いわゆる Open Session in View.
  • 永続オブジェクトをプレゼンテーション層で使わない.

要するに,

  • Hibernate セッションのスコープ ≧ 永続オブジェクトを使うスコープ

ということ.
永続オブジェクトはいくら POJO だとか言っても,普通のオブジェクトではないと思った方がいいんじゃないかな.それは固有のライフサイクルを持つので.「Hibernate in Action (isbn:193239415X)」P116 の図とか参照.
そのライフサイクルと密接に関連するのがセッションのスコープ.これ重要.間違いない.


さてさて,この問題に元記事の著者さんはいかに対処したのでしょうか?

Webアプリケーションの設計的には、接続セッションを表示層まで閉じずに持って行くことは無いでしょう。

ふむ.Open Session in View は採用しないわけですね.
いいでしょう.Hibernate セッションはトランザクション境界内にあるべきで,トランザクション境界はサービス層 (ドメイン層とかロジック層とか適当に読み替えて) にあるべきですから,Open Session in View が望ましくないのは同意.


ってことは,永続オブジェクトをプレゼンテーション層で使わないことにするしかないと思うわけですが...

この制限を回避するためにとる方法としてとることの出来る手法が四つあります(四つとも全然いけていませんが)。
  1. Lazyローディングをしない設定にする。
  2. 表示層で利用する関連については、制御層(コントローラ)で先に呼び出しておく
  3. マッピングオブジェクトを本当のPOJOにコピーして表示層に渡す
  4. 関連の設定を削除する

うーみゅ.
一つずつ見ていきますか.


まずは「Lazyローディングをしない設定にする」.
これは「ある程度の規模のデータベースではあり得ません」とのこと.
そうですね.不必要な関連まで全て辿っていたら大変なことになっちゃいますからねぇ.
そもそも Hibernate の遅延ロードって,ロードするためのものじゃなくてロードしないで済ませるためのものだと思っておいた方がよさげ.
「必要になったらロードする」じゃなくて,「必要じゃなければロードしない」を実現するのが遅延ロード,みたいな.
それによって,DB 内のオブジェクトグラフ全体をロードするのではなく,一部だけをロードできるようになるわけなので.
そんなわけで (どんなわけで?),これはいいでしょう.


次に「表示層で利用する関連については、制御層(コントローラ)で先に呼び出しておく」.
最初のやつを限定的に行うイメージですね.
で,ビューとして HTML を使う場合にはこれが現実的とのこと.本当?
さらに,

WebサービスFlash(Flex)との連携では表示層に渡されるオブジェクトのパブリックなアクセサが全て呼び出されるため、2の選択肢は利用できません。

ってことなんですが...
Flash (Flex) ではまだいいのかもしれないけれど (きっとよくないと思うけど),Web サービスで使うってことはそれは公開されたクラスってことでしょ? それを永続クラスなんていう内部的なものとしても使うっていうか,逆だな,内部的なものである永続クラスを Web サービスとして公開するってどうよ?
やっぱりね,インタフェースと実装の分離って大事だと思うわけですよ.
でね,それはレイヤアーキテクチャでも同じことだと思う次第.
永続オブジェクトってのはドメイン層における実装クラスなのであって,レイヤを超えて使うべきものではないと思われ.


次は「マッピングオブジェクトを本当のPOJOにコピーして表示層に渡す」.
この場合は「リストを毎回POJOに(しかも必要な関連のオブジェクトも含め)コピーするのは考えたくもありません」ということらしい.
惜しいなぁ.実に惜しいなぁ.
なぜコピーするのは考えたくもなかったのだろう? 実行時のコストが高いから? 開発時のコストが高いから?
本当に実行時のコストが問題になるかどうか測定してみたのでしょうか?
開発時のコストを低減できないか考えてみたのでしょうか?
そうですか,考えたくもありませんかそうですか.無念だ.


最後は「関連の設定を削除する」.
個人的にはあり得ねぇーって思うのですが,上の選択肢を全部否定してきたために「結局4にするのですが」ってことで,やっちゃったらしい (苦笑).
その結果,「hibenateでは関連のないテーブルとはJOINできなかったりします」ということになり,「シチュエーション2 Torque等のCriteria系になれている場合」の「関連を削除した時点で利用できませんが」へと繋がっていくわけですね...


そして「これで残る選択肢は無くなりました」ということに.マジですか?
なぜそんなことになったのか? Hibernate が悪いのか?
もちろん Hibernate が完璧ってことはないだろうけれど,その前にあるよね.ターニングポイントが.
言うまでもなく,「考えたくもありません」って奴.
考えることを放棄して思考停止しちゃったらそりゃダメだよ...
まぁ,彼が本当に何も考えなかったのか,単にブログ上でそう書いただけなのかは分かりませんけどね.


ともあれ (JW),元々の問題は永続オブジェクトを Hibernate セッションを超えてプレゼンテーション層でも使おうとして遅延初期化ができずに例外が吹っ飛んで来るというものでした.
そこで

  • Hibernate セッションのスコープをプレゼンテーション層まで広げる.
  • 永続オブジェクトをプレゼンテーション層で使わない.

のどちらかが基本的な対策になると思うのですが,元記事の著者さんはどっちも否定しちゃってるんですよね.
Hibernate セッションのスコープは広げたくない,でも永続オブジェクトをプレゼンテーション層でも使いたい,って.
割り切りが足りないっていうのか無い物ねだりっていうのか...
Open Session in View を使わないことにするなら,まずは永続オブジェクトを扱うスコープを狭くするということを前提として受け入れるべきだったんじゃないかなぁ.
「考えたくもない」なんて言わずにね.


もし Hibernate セッションおよび永続オブジェクトのスコープをドメイン層に限定した場合,プレゼンテーション層は永続オブジェクトとは異なるオブジェクトを使うことになります.
今は亡き「くーす」では DTO,そして「goya」では「プレゼンテーションモデル」と呼ばれているものですね.
これは要するに「3.マッピングオブジェクトを本当のPOJOにコピーして表示層に渡す」ということになります.
当然,永続オブジェクトを DTO なりプレゼンテーションモデルなりにコピーする手間はかかりますが,そこは工夫のしようがあると思うのですよね.
まぁ,もう少し待てばひがさんが Dxo なるものを作ってくれるみたいですが,そうでなくても BeanUtil みたいなのを使うとか自分でリフレクションばりばりするとかコードジェネレーションするとか,考えれば何か出来たんじゃないのかなぁ?


それから,DTO なりプレゼンテーションモデルなりを使うということは,それらへのコピーのために永続オブジェクトにアクセスするので,結果的に「2.表示層で利用する関連については、制御層(コントローラ)で先に呼び出しておく」と同じような状況になります.
ってことは,「この選択をした場合でも表示層の変更のために制御層を修正する必要がある」ということでもあります.プレゼンテーション層で新しいデータが必要ってことになると,DTO なりプレゼン (略) なりに新しい項目が追加されることになり,ドメイン層はそれを充足する必要があるためです.これってイマイチ?
でもでも,これって実は Open Session in View 使ってプレゼンテーション層で永続オブジェクトを遅延ロードしても避けられなかったりするんじゃないでしょうか?
だってだって,遅延ロードって結局は DB アクセスなわけで,やたらと遅延ロードしまくったらパフォーマンスが問題になる可能性が高いわけです.
そうすると,ドメイン層でまとめてフェッチしておくなどのチューニングが必要になってきたりするわけで,結局「表示層の変更のために制御層を修正する必要がある」ことになりかねないと思うんですよね.
それだったら,レイヤをまたがって DB アクセスを最適化するよりもドメイン層で閉じていた方が 264 倍マシだとか思ったり.
プレゼンテーション層っていうのはサービス層とかドメイン層とか呼ばれるレイヤにとってのクライアントで,クライアントの要求が変わったらサービスする側が影響を受けるのはある意味当然とも言えます.
一方で,ドメイン層の変更がそのクライアントであるところのプレゼンテーション層に影響することは極力避けたいわけで,やっぱりレイヤ間のインタフェースであるところの DTO なりプレ (略) なりと,ドメイン層における実装であるところの永続オブジェクトは明確に区別したいですね.


まとめると,

  • 永続オブジェクトは Hibernate セッションのスコープ内でのみ使うべき.
  • Hibernate セッションのスコープは (データベース) トランザクション境界内にあるべき.
  • (データベース) トランザクション境界はサービス層 (とかドメイン層とか) にあるべき.プレゼンテーション層まで広げるべきではない.

そんなわけで (どんなわけで?),

  • 永続オブジェクトはプレゼンテーション層で使うべきでない.

ということになります.
さらに,

  • 永続クラスはドメイン層における実装クラスである.
  • プレゼンテーション層とのインタフェースに実装クラスを使うべきではない.

ということからも,

  • 永続オブジェクトはプレゼンテーション層で使うべきでない.

ということになります.
これらの原則は,コピーの手間暇を考えたくもないというだけで破るような軽いものではないと個人的には思います.
たいていの場合,考えれば手間を減らすことは出来ますからね♪
もちろん,この結論が唯一絶対の真理ってことはありません.
でもでも,今の Hibernate だったら基本というか王道というかど真ん中はこうじゃないかなぁって思ってます.


むぅ...
元記事の著者さんがコメント書いてるなぁ.
でもでも,ここは読んでない... のか?
トラックバックに気づいていなかったのだろうか?
うーみゅ...