Hibernate EntityManagerを利用した感想

1ヶ月ほどご無沙汰してました。開発に没頭してましたが、そろそろ製造フェーズが終わりそうなので、ちょっと一息入れています。
Hibernateを今回初めて使ってみました。後、今思えばかなりチャレンジャーだったのですが(汗)、Hibernate EntityManagerをAPIとして利用しました。利用した理由はあまり技術的な理由ではなく、プロジェクト内のごたごたした関係が原因なので、あまり多くは触れません(苦笑)
さて、せっかく使ってみたので感想を書いてみようと思います。まず、JPAは結論として、かなり使えると思います。特にWebアプリケーションで典型的な処理(一覧表示、詳細表示、詳細の修正等)に関しては、きちんとDBのスキーマからEntityを起こして関連の定義さえ行ってあげれば、各業務のDBまわりの実装は驚く程少なくなります。今回自分は分離オブジェクトをmergeする手法で更新処理を行ったのですが、一度SELECTで取得したEntityに関しては、新たにQueryを発行する必要もなく更新してくれて、更に排他処理まで行ってくれたので非常に楽でした。
ただ、当然難点もありました。まず、主キー同士で結合を行っているようなテーブルの場合、関連先の「N対1の」テーブル情報が確実に存在する条件でない場合は、JPAの関連定義を行うのは非常に困難だと思います。以前自分がこの日記で色々悩んでいたのがそこだったのですが・・・JPAの関連定義って、つまりは外部キーを見てるわけですから、その外部キーが主キーになってしまうと、相手が存在するかどうかの判断が出来なくなってしまいます。つまり、自然キーやめて代理キーでテーブル設計してくれたら無問題なわけですが(笑)現実はなかなかそうもいかないわけで・・・
たまたま自分の仕事では、「関連するテーブルは必ず存在する」という条件がほとんどだったので事なきを得たのですが・・・もし関連先が存在しない条件が通ってしまう場合、遅延ロードを行っていると、存在しない筈のフィールドに偽のProxyが存在してしまうことになり、そのProxyからデータを取得しようとした時点でエラーになります(本来はオブジェクトがnullの筈なのに)。今回、単体テスト中にこのエラーで質問を受けることが多かったのですが、調べてみると「テスト用のデータを手抜きしてて、存在する筈のデータを入れてなかった」・・・というのが殆どでした(汗)まぁここら辺は自分の周知が徹底してなかったせいですけど。
Hibernateはよく「複合主キーでは使えない」と言われますがそうではなくて、結局は「複合主キーを使ってるようなテーブルは大抵自然キーを使っており、結果として主キー同士で結合を行っている」ことがネックになっているのかなと思います。つまり「代理キーを使ってないテーブルの場合は、結合条件がしっかり定義出来るかどうかをチェックする。更に遅延ロードを利用する場合は結合先が必ず存在するかどうかをチェックする。チェックに引っかかった場合はかなり面倒なことになると思われるので、JPAの採用は考えた方がいい」ということでしょうか。どんなDB設計に対してもJPAが適用出来るかというとそうではなくて、採用を検討しているシステムのDB設計とよく照らし合わせて、適用を判断することが必要かと思われます。
また、JPAという仕様そのものについてですが、初めてのバージョン(実質はEJBのバージョン3ですが)ということもあって、まだ全ての部分を網羅しているとは言い難い状況です。その典型的な例がSQLのサポートです。JPAでは一応SQLをサポートするAPIを備えていますが、はっきり言って使い物になりません。そして、EJB-QL(HQL)は、SQLの大部分をサポートする、かなり使い勝手の良いQueryですが、それでもやはり出来ないことがいくつかあります。たとえば、FROM句に副問い合わせが使えませんし、UNIONも使えません。テーブルの結合条件では結合出来ないテーブルを外部結合で取得したい場合(ややこしい・・・)にも、EJB-QLは使えません。
これらに合致してしまった場合はSQLを使うことになるのですが、JPASQLを使う場合、戻り値をアノテーションで定義して、更にそれをEntityクラスのどこかに記述してあげる必要があります。もうこの時点で自分は使う気無くなりました・・・
結局、SQLを使う局面は必ず出てくると思いますし、そのときはJPAの実装フレームワークHibernateTopLink)の機能に頼った方が良いのではないかと思います。自分の場合は、HibernateのSQLQueryを利用してSQLを記述しました。このSQLQueryですが、これもまた戻り値をオブジェクトに詰める作業が結構面倒で、DbUtils等のラッピングツールから比べて退化してるんじゃないかと思ってしまう出来なのですが・・・それでもまだ使えるレベルです。また、純粋なSQLの記述ではなくなってしまうのですが、戻り値を複数のEntityに分けてグループ化し、戻り値をEntityで取得することも出来ます。一端Entityを取得することさえ出来れば、後の動作は同じですので、これは結構便利な機能だなと思いました。
他にも、Lock機能(SELECT FOR UPDATE発行)もJPAはあまり使い物にならなくて、これもHibernateのSessionの機能を使ってます。それと、EventListener機能ですが、JPAが提供するListenerは、旧データと新データを比較できるような引数が無かったり、対象オブジェクトを引数でもらえるような機能も無かったりするので、、これもまたあまり使えません。Hibernateはバージョン3から強力なEventListener機能を備えていますので、自分は主にそれを利用しました。
現時点では、JPAはあくまで「統一的に利用できるAPI」と見なした方がよさげです。実装フレームワークの依存性を排除できる段階にはまだ達しておらず、今の時点で使うには、実装フレームワークHibernateTopLink)の知識は必須ではないかと思われます。
あと、これはJPAというよりHibernateに対しての感想ですが・・・以前にも軽く触れましたが、Hibernateというツールは決してSQLを「隠蔽」するツールではなく、SQLを「生成」するツールなんだなという印象を今は持っています。Hibernate in Actionにも書いてありましたが、「HibernateSQLを熟知した開発者こそが使うツールである」という基本方針を知らず、SQLを書きたくないという理由で採用してはいけないツールだと感じました。つまり、SQLに対して積極的・肯定的でなければ、このツールは使えない。EJB2以前の考え方とは対極的なわけで、そういう意味でEJB3のEntityBeanは2以前とは正反対のものになったと考えるべきでしょう。
ただ・・・ツールとして、そこまでSQLを肯定してるのに、肝心のSQLの使い勝手が悪いのはどうなのかと(苦笑) HQLでほとんどの要件をカバー出来る筈というのがその根本にあるみたいですが・・・それは理想論の気がします。何といっても、基本がSQLにあるのなら、そのSQLをそのまま書きたいという欲求は当然だし、SQLが出来ることが全て出来ない限り、完璧なO/Rマッピングツールとは言えないのではないか・・・と今は思います。まぁ、Hibernateというプロジェクトは驚くくらい早いスピードで開発が行われてますから、そのうちこういった意見も汲み取ってくれる筈だ・・・と勝手に期待していますが。
今後の展開はよく知りませんが・・・一つ思うのは、JPA対応のO/Rマッピングツールが、その内部にDIコンテナを持っていたら面白いだろうなと感じてます。例えば、遅延ロードはProxyで制御するよりも、呼び元のgetterメソッドに対してAspectをかけて制御した方がより使い勝手が良くなる気がしますし、あとEntityに対してStrategy等のオブジェクトをDIしたいという欲求もあります。今の状態だと、Entityに対してロジックを書きたくなる欲求が強くなるのですが、Entityにそのロジックをベタ書きしてしまうと、結果としてEntityがなにやらでかくなってしまいます。DIコンテナによってせっかくロジックをクラス分割して綺麗に書ける状況が出来つつあるのですから、その考えをぜひEntityに対して適応してみたいと思うのですが・・・こんな需要は今どのくらいあるんだろう?