Hibernate は O/R マッピングツールと呼ばれる、リレーショナルデータベースとオブジェクトモデルとの間を埋めるフレームワークです。
関連するテーブルのオブジェクトを管理する方法が、十数種類あります。Index of Relationships< のサイトに、Hibernate でサポートされる関係の一覧が載っています。
ドキュメントも豊富にそろっていて、リファレンスマニュアル (日本語翻訳済み) が非常に参考になります。マニュアルの中から、気になった部分や使えそうな部分だけを抜き出しておきます。
[参考]
Hibernate の基本的なこと
- Hibernate で表現できる関係
- HQL 文を書くときの注意点
- マッピングファイルについてあれこれ
- SpringFramework との連携時、テストでLazyInitializationExceptionが出る場合の対処
- org.hibernate.HibernateException: CGLIB Enhancement failed: <className>
Hibernate で表現できる関係
Hibernate はリレーショナルデータベースのほとんどの関係を表現することが出来ます。その一部が下記のような関連です。
- 1. One-to-one (一対一)
- 2つのオブジェクトが同一のIDを共有する場合。親子関係がこれにあたります。
- 2. One-to-many (一対多)
- オブジェクトのプロパティがコレクションの場合。Hibernate は、コレクションに Set, List, Bag, 独自のコレクション型が使用できます。
Bag は Set と似ていて、等価なものを一意に扱います。ただし、同じオブジェクトを2回以上登録できます。同じオブジェクトが何回登録されたかを管理できます。
- 3. Many-to-many (多対多)
- 双方向の関係で、多対一の関係の場合。両者のオブジェクトのプロパティに、相手のクラスのコレクションが保持されている状態。
- 4. Inheritance (継承)
- 2つのオブジェクトに is-a の関係がある場合。テーブル的には、同一のテーブルに格納される場合。もしくは、同一の主キーをもつテーブルが個々のクラスに対して存在する。
- 5. Component (コンポーネント)
- 複数のクラスを一つのテーブルにマッピングする方法の一つ。
[参考]
HQL 文を書くときの注意点
HQL の基本的な文法はリファレンスマニュアル を参考にしてください。
(基本事項抜粋)
HQLはクエリの大文字・小文字を区別しない
ただし、HQL中のオブジェクト参照(プロパティ)は大文字・小文字の区別をします。
from 句に書くのは、クラス名
大文字・小文字の区別が必要です。パッケージを含めた完全クラス名を指定することもできます。マッピングファイルで auto-import="false" を指定した場合は、完全クラス名を指定する必要があります。
HQL中でクラスのプロパティを指定するときは、クラス名に別名を付ける必要がある
クラスの別名は、 from Worker as w のように as を使って指定します。HQL は、結構複雑なものまで書けるので、それなりに使えるかも。
バインド変数には、名前を付ける
HQL 文には、バインド変数を使うことが出来ます。 PreparedStatement でおなじみの "?" と、:id のような、任意の名前を与える方法が使えます。"?" を使った場合には、インデックス順にバインド値を設定しなければなりません。名前を与えた場合には、Query オブジェクトを使って、任意の順番でバインドできます。
Query q = s.createQuery("from Worker as w where w.id = :id1 or w.id = :id2");
q.setParameter("id1", new Integer(1), Hibernate.INTEGER);
q.setParameter("id2", new Integer(2), Hibernate.INTEGER);
クエリの本体をマッピング文書内に定義する
クエリ文字列をJavaコードの外に定義することができます。CDATAセクションを使って定義します。
Worker.hbm.xml
<hibernate-mapping>
<query name="com.hamasyou.hibernate.query.methodName">
<![CDATA[
from com.hamasyou.hibernate.entity.Worker as worker
where worker.age <= :age
]]>
</query>
</hibernate-mapping>
呼び出すには、プログラムから、クエリ名を指定すればよいです。
Query q = session.getNamedQuery("com.hamasyou.hibernate.query.methodName");
q.setInteger("age", new Integer(22));
マッピングファイルについてあれこれ
マッピングファイルの粒度
- マッピングファイルは、クラス毎に作成する
- マッピング情報は XDoclet で書く
- XDoclet で書くことのメリットは、クラスとマッピングの対応が分かりやすいこと
- デメリットは、マッピングファイルに手を入れたときに、上書きされてしまう可能性があること
マッピング情報は、XDoclet の形式で、ソースコード中に埋め込むのが良いような気がします。確かに、POJO のクラスに Hibernate の情報が入り込むのは、別のフレームワークを使おうとした場合に不利ではありますが、たぶんほとんどの場合そういうことはないはずです。XDoclet で定義すると、クラスとマッピングが同じ場所に存在することになるので、対応が分かりやすいというメリットがあります。
とはいえ、やっぱり XDoclet の形式で書くよりも XML で記述したほうが 保守はしやすいか・・・。
[参考]
XDoclet 形式でマッピングを書いた場合問題となるのが、生成されたマッピングファイルを手で修正していた場合に、もう一回ファイルを生成すると上書きされてしまうことです。マージファイルを使って、上手く上書きされることがないようにする必要があります。
マージファイルを使う方法
Ant の Hibernate XDoclet の mergedir に設定したフォルダの下に、パッケージを作り、hibernate-properties-{クラス名}.xml というファイルを置いておきます。中身がそのままマージファイルとして所定の位置に埋め込まれます。XDoclet として書けない静的なプロパティなどを、このファイルに書いておきます。
ちなみに、JBoss-IDE という Eclipse のプラグインを使うと XDoclet の補完が効くようなって便利です。
Hibernate 用の XDoclet 解析 Ant スクリプト例
<!-- パスの設定 -->
<path id="id.xdoclet.classpath">
<fileset dir="C:\\java\\XDoclet\\xdoclet-1.2.2"
includes="*.jar"/>
</path>
<!-- Hibernate XDoclet の解析 -->
<target name="xdoclet">
<taskdef name="hibernatedoclet"
classname="xdoclet.modules.hibernate.HibernateDocletTask"
classpathref="id.xdoclet.classpath"/>
<hibernatedoclet destdir="${src.dir}"
excludedtags="@version,@author,@todo"
force="false"
mergedir="."
verbose="false">
<fileset dir="${src.dir}" includes="**/*.java"/>
<hibernate version="2.0"/>
</hibernatedoclet>
</target>
XDoclet で書くときの注意点
@hibernate.id タグには、主キー項目を指定します。このとき、type 属性を付けると、composite-id だと解釈されるのか、エラーがでます。type 属性は Hibernate が適切なマッピングを自動で探してくれるので、ほとんどの場合指定しなくていいようです。ちなみに、指定する場合は、 HIbernate 型で指定します。
主キーの項目の型
主キー項目の型は、null が代入できる型にしておいた方がいいみたいです。つまり、オブジェクト型にしておくといいです。Hibernate は永続化されたかどうかを判断するのに、主キー項目の値が null かどうかで判定します (デフォルトの動作)。永続化されたかどうかの判断に別の値を指定したい場合には、 @hibernate.id unsaved-value を設定します。
ビジネスキーの同値性ってどいういこと!?
Hibernate は、テーブルの主キーとオブジェクトIDとを別々に考えるようです。
なんですが,単純に ID プロパティの同値性を判断してもいけないみたい.というのもですね,永続クラスの新しいインスタンスが生成された時点では,ID プロパティの値は設定されないらしいんですね.あう,だから ID プロパティは null にできる型を推奨なのでしょうか? そんな気がする.
そんなわけで,結局のところは 「ビジネスキー」 の同値性を使って判定しろとのことです... つまり Hibernate を使う際には,主キーは Hibernate 用に INTEGER とかで無意味な ID にしておいて,業務的なキーは主キーにするなってことですか? まぁ,主キーは候補キーの一つに過ぎないというのも事実な訳ですが... DB屋さんに反発食らわないかなぁ?(^^;
主キーと候補キー
候補キーというのは、エンティティを一意に識別する属性の、最小単位の集合のことです。社会保障番号や、DNA なんかは、人を特定する候補キーになります。
主キーは、候補キーから選ばれたデータベース上のレコードを一意に識別するキーのことです。つまり、候補キーから選ばれます。単に、シーケンシャルな値でもかまいません。要は NULL を含まず、レコードが一意に識別できれさえすればいいのです。
つまり、主キーとは、RDB が便宜的に使うキーなのです。Hibernate では、オブジェクトID は、RDB に格納された段階で決まります。つまり、オブジェクトID は主キーなのです。
HQL を使って外部結合
HQL を使って検索する場合、find() メソッドを使って外部結合を行うには明示的にHQL文を書く必要があるみたいです。詳しくは、「Hibernate 入門記 - HQL と outer-join 属性」 を参考に。
SpringFramework との連携時、テストでLazyInitializationExceptionが出る場合の対処
SpringFramework と Hibernate を連携させてコーディングを行う場合、SessionFactory を SpringFramework から提供してもらうようにします。これは、トランザクション管理を、コンテナに任せようという意図で、SpringFramework + Hibernate では一般的に行われる処理です。
SpringFramework + Hibernate で Web アプリケーションを作る場合、「Open Session In View」という手法を使って、Session オブジェクトのオープンとクローズを、View で行うという方法をとることがあります。これは、Hibernate で Lazy load(遅延ロード)を行う場合に、View で遅延ロードが行われる場合があり、Session が閉じられてしまって、LazyInitializationException がでてしまうことがあるからです。
Web で使う場合はいいのですが、単体テストなどで、SpringFramework + Hibernate を使いたい場合、Session のオープンとクローズを View で行うことができません(Open Session In View を使うと、Webに依存したテストになってしまうから)。そこで、TransactionSynchronizationManager というクラスを使って、テスト時に、Session 管理を行う方法が、「Open Session in View and testing」 に載っています。これで、テスト時にも、SpringFramework + Hibernate でテストできるようになります。
org.hibernate.HibernateException: CGLIB Enhancement failed: <className>
classNameで定義されているクラスのコンストラクタが private になっているのが原因かもしれません。クラスをインスタンス化できない場合にスローされます。
対処方法は、例外の出ているクラスのコンストラクタを パッケージプライベート、protected、publicのいずれかに変更します。
参考
Hibernate のリファレンスドキュメント (日本語) Hibernate Reference Document
Hibernate だけでなく、データベース設計にまで触れられていておすすめ。
- 薄いながらも十分な情報量。HibernateとSpringにも触れられています。
- Hibernate の基本的な使い方が載っています。
- 開発者のための Hibernate の解説書が日本語で登場しました。