Hibernate は O/R マッピングツールと呼ばれる、リレーショナルデータベースとオブジェクトモデルとの間を埋めるフレームワークです。

関連するテーブルのオブジェクトを管理する方法が、十数種類あります。Index of Relationships< のサイトに、Hibernate でサポートされる関係の一覧が載っています。

ドキュメントも豊富にそろっていて、リファレンスマニュアル (日本語翻訳済み) が非常に参考になります。マニュアルの中から、気になった部分や使えそうな部分だけを抜き出しておきます。

[参考]

Hibernate の基本的なこと

  1. Hibernate で表現できる関係
  2. HQL 文を書くときの注意点
  3. マッピングファイルについてあれこれ
  4. SpringFramework との連携時、テストでLazyInitializationExceptionが出る場合の対処
  5. 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 になっているのが原因かもしれません。クラスをインスタンス化できない場合にスローされます。

対処方法は、例外の出ているクラスのコンストラクタを パッケージプライベートprotectedpublicのいずれかに変更します。

参考

  • Hibernate のリファレンスドキュメント (日本語) Hibernate Reference Document

  • Hibernate だけでなく、データベース設計にまで触れられていておすすめ。

  • 薄いながらも十分な情報量。HibernateとSpringにも触れられています。
  • Hibernate の基本的な使い方が載っています。
  • 開発者のための Hibernate の解説書が日本語で登場しました。