第5章 O/Rマッピングの基本

5.1. マッピング定義

オブジェクト/リレーショナル・マッピングは、XMLドキュメントで定義します。 マッピング・ドキュメントは、読みやすく手作業で編集しやすいようにデザインされています。 マッピング言語はJava中心、つまりテーブル定義ではなく、 永続クラス定義に基づいて構築されるマッピングとなっています。

Hibernateユーザの多くはXMLマッピングを手作業で行いますが、 XDoclet, Middlegen, AndroMDAというようなマッピング・ドキュメント生成ツールが いくつか存在します。

例題のマッピングから始めましょう:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping package="eg">

        <class name="Cat" table="CATS" discriminator-value="C">
                <id name="id" column="uid" type="long">
                        <generator class="hilo"/>
                </id>
                <discriminator column="subclass" type="character"/>
                <property name="birthdate" type="date"/>
                <property name="color" not-null="true"/>
                <property name="sex" not-null="true" update="false"/>
                <property name="weight"/>
                <many-to-one name="mate" column="mate_id"/>
                <set name="kittens">
                        <key column="mother_id"/>
                        <one-to-many class="Cat"/>
                </set>
                <subclass name="DomesticCat" discriminator-value="D">
                        <property name="name" type="string"/>
                </subclass>
        </class>

        <class name="Dog">
                <!-- ここにDog用のマッピング書きます -->
        </class>

</hibernate-mapping>

それでは、マッピング・ドキュメントの内容について述べていきます。 ただし、Hibernateが実行時に使うドキュメント要素と属性についてだけ述べます。 マッピング・ドキュメントは、スキーマ・エクスポート・ツールがエクスポートする データベース・スキーマに影響を与える、 いくつかのオプションの属性と要素も含みます(例えば not-null 属性)。

5.1.1. Doctype

XMLマッピングでは、必ずお見せしたようなdoctypeを定義すべきです。 実際のDTDは、上記のURLまたは hibernate-x.x.x/src/net/sf/hibernate または hibernate.jar ディレクトリにあります。 Hibernateは常に、クラスパスでDTDを探し始めます。

5.1.2. hibernate-mapping

この要素には、オプションの属性が3つあります。 schema 属性では、 このマッピングから参照するテーブルが、その名前のスキーマに属することを指定します。 指定されると、テーブル名は与えられたスキーマ名で修飾されます。 そうでなければ、テーブル名は修飾されません。 default-cascade 属性では、 cascade 属性を指定していないプロパティやコレクションを、 どのカスケード・スタイルと解釈すべきかを指定します。 auto-import 属性は、 クエリ言語で、修飾されていないクラス名をデフォルトで使えるようにします。

<hibernate-mapping
         schema="schemaName"                          (1)
         default-cascade="none|save-update"           (2)
         auto-import="true|false"                     (3)
         package="package.name"                       (4)
 />
(1)

schema(オプション):データベース・スキーマの名前。

(2)

default-cascade(オプション - デフォルトは none): デフォルトのカスケード・スタイル。

(3)

auto-import (オプション - デフォルトは true): クエリ言語で(このマッピングのクラスの) 修飾されていないクラス名を使えるかどうかを指定します。

(4)

package(オプション): マッピング・ドキュメントの中の修飾されていないクラス名に使う、 パッケージ・プリフィックスを指定します。

(修飾されていない)同じ名前の永続クラスが2つあるなら、 auto-import="false" を設定すべきです。 もし2つのクラスに同じ「インポートされた」名前を割り当てようとすると、 Hibernateはエラーをスローします。

5.1.3. class

class 要素で、永続クラスを定義できます:

<class
        name="ClassName"                              (1)
        table="tableName"                             (2)
        discriminator-value="discriminator_value"     (3)
        mutable="true|false"                          (4)
        schema="owner"                                (5)
        proxy="ProxyInterface"                        (6)
        dynamic-update="true|false"                   (7)
        dynamic-insert="true|false"                   (8)
        select-before-update="true|false"             (9)
        polymorphism="implicit|explicit"              (10)
        where="arbitrary sql where condition"         (11)
        persister="PersisterClass"                    (12)
        batch-size="N"                                (13)
        optimistic-lock="none|version|dirty|all"      (14)
        lazy="true|false"                             (15)
/>
(1)

name:永続クラス(またはインターフェイス)の完全修飾Javaクラス名。

(2)

table:そのデータベース・テーブルの名前。

(3)

discriminator-value(オプション - デフォルトはクラス名): ポリモーフィックな振る舞いのために使われる、個々のサブクラスを区別するための値。 値は nullnot null のいずれかです。

(4)

mutable(オプション - デフォルトは true): クラスのインスタンスが更新可能(不可能)であることを指定します。

(5)

schema(オプション): ルートの <hibernate-mapping> 要素で指定されたスキーマ名を、 オーバーライドします。

(6)

proxy(オプション): lazy初期化プロキシに使うインターフェイスを指定します。 永続化するクラスの名前そのものを指定できます。

(7)

dynamic-update(オプション - デフォルトは false): 実行時に、値の更新されたカラムだけに対する UPDATE SQLを生成することを指定します。

(8)

dynamic-insert(オプション - デフォルトは false): 実行時に、nullでないカラムだけを含む INSERT SQLを生成することを指定します。

(9)

select-before-update(オプション - デフォルトは false): オブジェクトが実際に更新されたのが確実でなければ、 HibernateがSQL UPDATE決して実行しない ことを指定します。 特定の場合(実際には、一時的オブジェクトが update() を使い、 新しいSessionに関連付けられているときだけ)、 実際に UPDATE が必要かどうかを決定するために、 余分なSQL SELECT が実行されることを意味します。

(10)

polymorphism(オプション - デフォルトは implicit): 暗黙的(implicit)か明示的(explicit)、どちらのポリモーフィズムを使うかを決定します。

(11)

where(オプション): このクラスのオブジェクトを復元するときに使われる、 任意のSQL WHERE 条件を指定します。

(12)

persister(オプション): カスタム ClassPersister を指定します。

(13)

batch-size(オプション - デフォルトは 1): 識別子でこのクラスのインスタンスを復元するときの「バッチ・サイズ」を指定します。

(14)

optimistic-lock(オプション - デフォルトは version): 楽観的ロック戦略を指定します。

(15)

lazy(オプション): lazy="true" と設定することは、 プロキシ インターフェイスとして 永続化するクラスの名前そのものを指定することと同じです。

永続クラスの名前にインターフェイスを指定しても、全く問題ありません。 <subclass> 要素で、そのインターフェイスの実装クラスを定義します。 どんな static 内部クラスでも永続化できます。 例えば eg.Foo$Bar のように、標準形式を使いクラス名を指定すべきです。

mutable="false" と指定された更新不能クラスは、 アプリケーションから更新や削除をできません。 これにより、Hibernateのパフォーマンスが少しだけ良くなります。

オプションの proxy 属性を指定すると、 クラスの永続インスタンスのlazy初期化ができるようになります。 Hibernateは最初に、名前付きのインターフェイスを実装したCGLIBプロキシを返します。 実際の永続オブジェクトは、プロキシのメソッドが起動されたときにロードされます。 以下の「lazy初期化のためのプロキシ」を見てください。

暗黙的(Implicit) ポリモーフィズムとは、 スーパークラスや実装するインターフェイスを指すクエリによって、 そのクラスのインスタンスが返されることを意味します。 そしてそのサブクラスのインスタンスは、そのクラス自体の名前が指定されたときだけ返されます。 明示的(Explicit) ポリモーフィズムとは、 クラス名が明示的に指定されたときにだけ、そのインスタンスが返されることを意味します。 そして <class> 要素の中で <subclass><joined-subclass> とマッピングされているサブクラスのインスタンスだけが返されます。 ほとんどの用途ではデフォルトの polymorphism="implicit" が適切です。 明示的ポリモーフィズムは、異なった2つのクラスが同じテーブルにマッピングされているときに 役立ちます (これはテーブルのカラムのサブセットを含む「軽量」クラスを可能にします)。

persister 属性を使うと、 クラスの永続戦略をカスタマイズすることができます。 例えば net.sf.hibernate.persister.EntityPersister のサブクラスを指定することができ、 また例えばストアド・プロシージャ・コール、フラット・ファイルのシリアライゼーション、 LDAPなどを通して永続性を実装する net.sf.hibernate.persister.ClassPersister インターフェイスの 完全に新しい実装を用意することさえできます。 簡単な例として net.sf.hibernate.test.CustomPersister を見てください(これは Hashtable の「永続化」です)。

dynamic-updatedynamic-insert の設定は、 サブクラスに継承されないことに注意してください。 そのため <subclass><joined-subclass> 要素を指定することもできます。 この設定はパフォーマンスを向上させることも、低下させることもあります。 賢く使ってください。

select-before-update を使うと、 普通はパフォーマンスが低下します。 不必要にデータベース更新トリガがコールされないようにするためには 非常に有効です。

dynamic-update を有効にすれば、 楽観的ロック戦略を選ぶことになります:

  • version バージョン/タイムスタンプ・カラムをチェックします

  • all すべてのカラムをチェックします

  • dirty 変更されたカラムをチェックします

  • none 楽観的ロックを使いません

Hibernateで楽観的ロックを使うなら、 バージョン/タイムスタンプ・カラムを使うことを 非常に 強くおすすめします。 これはパフォーマンスの点から見て最適な戦略です。 さらにSessionの外での修正 (つまり Session.update() が使われるとき) を正確に扱うことのできる唯一の戦略でもあります。 どの unsaved-value 戦略を選ぶとしても、 バージョンまたはタイムスタンプ・プロパティはnullではいけないことを 心に留めておいてください。 そうでなければ、インスタンスは一時的なものだと受け取られてしまいます。

5.1.4. id

マッピングするクラスには、データベース・テーブルの主キーカラムを定義しなければ なりません。 ほとんどのクラスにはインスタンスのユニークな識別子を保持する JavaBeansスタイルのプロパティもあります。 <id> 要素は、 そのプロパティから主キーカラムへのマッピングを定義します。

<id
        name="propertyName"                      (1)
        type="typename"                          (2)
        column="column_name"                     (3)
        unsaved-value="any|none|null|id_value"   (4)
        access="field|property|ClassName">       (5)

        <generator class="generatorClass"/>
</id>
(1)

name(オプション):識別子プロパティの名前。

(2)

type(オプション):Hibernate型を示す名前。

(3)

column(オプション - デフォルトはプロパティ名): 主キーカラムの名前。

(4)

unsaved-value(オプション - デフォルトは null): インスタンスが、新しくインスタンス化された (セーブされていない)ことを示す識別子プロパティの値。 以前のSessionでセーブまたはロードされた一時的インスタンスと区別するために 使います。

(5)

access(オプション - デフォルトは property): プロパティの値へのアクセスに、Hibernateが使う戦略

name 属性がなければ、クラスには識別子プロパティがないものとみなされます。

unsaved-value 属性は重要です。 クラスの識別子プロパティがデフォルトで null でなければ、 実際のデフォルト値を指定すべきです。

複合キーを持つレガシー・データにアクセスするために、 <composite-id> という代わりの方法があります。 他の用途への使用は全くおすすめできません。

5.1.4.1. generator

必須の <generator> 子要素は、 永続クラスのインスタンスのユニークな識別子を生成するために使う、Javaクラスを指定します。 ジェネレータ・インスタンスを設定または初期化するためにパラメータが必要なら、 <param> 要素を使い、渡せます。

<id name="id" type="long" column="uid" unsaved-value="0">
        <generator class="net.sf.hibernate.id.TableHiLoGenerator">
                <param name="table">uid_table</param>
                <param name="column">next_hi_value_column</param>
        </generator>
</id>

すべてのジェネレータは、インターフェイス net.sf.hibernate.id.IdentifierGenerator を実装します。 これはとても単純なインターフェイスなので、 いくつかのアプリケーションでは独自に特別な実装を用意するかもしれません。 しかしHibernateは組み込みの実装をいくつも用意しています。 組み込みのジェネレータにはショートカット名があります:

increment

long, short, int 型の識別子を生成します。 それらは他のプロセスが同じテーブルにデータを挿入しないときだけユニークです。 クラスタでは使わないでください。

identity

DB2, MySQL, MS SQL Server, Sybase, HypersonicSQLのアイデンティティ・カラムを サポートします。 返される識別子の型は long, short, int のいずれかです。

sequence

DB2, PostgreSQL, Oracle, SAP DB, McKoiのシーケンス、 またはInterbaseのジェネレータを使います。 返される識別子の型は long, short, int のいずれかです。

hilo

long, short, int 型の識別子を効率的に生成するhi/loアルゴリズムを使います。 hi値のソースとして、テーブルとカラムを与えます (デフォルトでは hibernate_unique_keynext_hi それぞれが)。 hi/loアルゴリズムは特定のデータベースだけでユニークな識別子を生成します。 JTAに登録されているコネクションや ユーザが用意したコネクションには、このジェネレータを使わないでください。

seqhilo

long, short, int 型の識別子を効率的に生成するhi/loアルゴリズムを使います。 名前付きのデータベース・シーケンスを与えます。

uuid.hex

ネットワークでユニークなstring型の識別子を生成する、 128ビットUUIDアルゴリズムを使います(IPアドレスを使います)。 UUIDは長さ32の16進数の文字列としてエンコードされます。

uuid.string

同じUUIDアルゴリズムを使います。 UUIDはASCII文字からなる長さ16の文字列にエンコードされます。 PostgreSQLでは使わないでください。

native

データベースにより identity, sequence, hilo のいずれかが選ばれます。

assigned

save() がコールされる前に、 アプリケーションがオブジェクトに識別子を代入することを可能にします。

foreign

他の関連オブジェクトの識別子を使います。 普通は、 <one-to-one> 主キー関連で使います。

5.1.4.2. Hi/Loアルゴリズム

hiloseqhilo ジェネレータは、 hi/loアルゴリズムの2つの選択可能な実装を提供します。 これは識別子生成の好ましいアプローチです。 1番目の実装は、次回に利用される"hi"値を保持する「特別な」データベース・テーブルを 必要とします。 2番目の実装は、Oracleスタイルのシーケンスを使います(サポートされている場合)。

<id name="id" type="long" column="cat_id">
        <generator class="hilo">
                <param name="table">hi_value</param>
                <param name="column">next_value</param>
                <param name="max_lo">100</param>
        </generator>
</id>
<id name="id" type="long" column="cat_id">
        <generator class="seqhilo">
                <param name="sequence">hi_value</param>
                <param name="max_lo">100</param>
        </generator>
</id>

あいにく、独自に Connection を用意するときや、 JTAに登録されているコネクションを取得するために アプリケーション・サーバ・データストアを使うときは、 hilo を使えません。 Hibernateは、新しいトランザクションで"hi"値を取得できなければなりません。 EJB環境での標準の方法は、ステートレス・セッション・ビーンを使い、 hi/loアルゴリズムを実装する方法です。

5.1.4.3. UUIDアルゴリズム

UUIDには以下のものが含まれます: IPアドレス、JVMのスタートアップ・タイム(4分の1秒の正確さ)、 システム時間、(JVMに対してユニークな)カウンタ値。 JavaコードからMACアドレスやメモリ・アドレスを取得することはできないので、 JNIが使えないときには最良の方法です。

uuid.string は、PostgreSQLでは使わないでください。

5.1.4.4. アイデンティティ・カラムとシーケンス

アイデンティティ・カラムをサポートしているデータベース(DB", MySQL, Sybase, MS SQL)では、 identity キー生成を使えます。 シーケンスをサポートするデータベース(DB2, Oracle, PostgreSQL, Interbase, McKoi, SAP DB)では、 sequence スタイルのキー生成を使えます。 これらの戦略は両方とも、新しいオブジェクトを挿入するために、 SQLクエリを2つ必要とします。

<id name="id" type="long" column="uid">
        <generator class="sequence">
                <param name="sequence">uid_sequence</param>
        </generator>
</id>
<id name="id" type="long" column="uid" unsaved-value="0">
        <generator class="identity"/>
</id>

クロスプラットフォームの開発では、native 戦略は identity, sequence, hilo 戦略の中から1つを選択しますが、 これはデータベースの能力に依存します。

5.1.4.5. 代入識別子

(Hibernateが生成するものではなく)アプリケーションに識別子を代入させたければ、 assigned ジェネレータを使うことができます。 この特別なジェネレータは、すでにオブジェクトの識別子プロパティに代入された値を 識別子に使います。 この機能をビジネス上の意味として使う場合は十分に注意してください (ほとんどの場合は悪い設計といえます)。

固有の性質として、このジェネレータを使うエンティティは、 SessionのsaveOrUpdate()メソッドでセーブできません。 その代わりに、Sessionの save() または update() メソッドをコールすることで、 オブジェクトをセーブまたは更新すべきかを、 明示的にHibernateに指定します。

5.1.5. composite-id

<composite-id
        name="propertyName"
        class="ClassName"
        unsaved-value="any|none"
        access="field|property|ClassName">

        <key-property name="propertyName" type="typename" column="column_name"/>
        <key-many-to-one name="propertyName class="ClassName" column="column_name"/>
        ......
</composite-id>

複合キーのあるテーブルに対して、 クラスの複数のプロパティを識別子プロパティとしてマッピングすることができます。 <composite-id> 要素は、 <key-property> プロパティ・マッピングと <key-many-to-one> マッピングを子要素として受け入れます。

<composite-id>
        <key-property name="medicareNumber"/>
        <key-property name="dependent"/>
</composite-id>

複合識別子の等価性を実装するためには、永続クラスは equals()hashCode() をオーバーライド しなければなりません。 また Serializable も実装しなければいけません。

あいにく複合識別子のためのこの方法は、 永続オブジェクト自体が自分の識別子であることを意味しています。 オブジェクトそのものを「扱う」以上の便利さはありません。 永続クラス自体のインスタンスをインスタンス化しなければならず、 また複合キーに関連した永続状態を load() できるようになる前に、 識別子プロパティを設定しなければなりません。 別クラスとして複合識別子が実装されている場合の、 より便利な方法について 項7.4. 「複合識別子としてのコンポーネント」 で述べます。 以下で述べる属性は、この代わりの方法だけに当てはまります:

  • name(オプション): 複合識別子を保持するコンポーネント型のプロパティ(次の節を見てください)。

  • class (オプション - デフォルトはリフレクションにより決定されるプロパティの型): 複合識別子として使われるコンポーネントのクラス(次の節を見てください)。

  • unsaved-value (オプション - デフォルトは none): any と設定されると、 一時的インスタンスが新しくインスタンス化されたことを示します。

5.1.6. discriminator

<discriminator> 要素は、 table-per-class-hierarchyマッピング戦略を使うポリモーフィックな永続化に必要で、 テーブルのディスクリミネータ・カラムを定義します。 ディスクリミネータ・カラムは、ある行に対して永続層がどのサブクラスをインスタンス化するかを 伝えるマーカー値を含んでいます。 以下のような型に制限されます:string, character, integer, byte, short, boolean, yes_no, true_false.

<discriminator
        column="discriminator_column"  (1)
        type="discriminator_type"      (2)
        force="true|false"             (3)
        insert="true|false"            (4)
/>
(1)

column(オプション - デフォルトは class): ディスクリミネータ・カラムの名前。

(2)

type (オプション - デフォルトは string):Hibernate型を示す名前。

(3)

force (オプション - デフォルトは false): ルート・クラスのすべてのインスタンスが復元されたときでも、 Hibernateに許可されたディスクリミネータ・カラムの指定を「強制」します。

(4)

insert (オプション - デフォルトは true): false に設定すると、 ディスクリミネータ・カラムがマッピングされる複合識別子の一部になります。

ディスクリミネータ・カラムの実際の値は、 <class><subclass> 要素の discriminator-value 属性で指定されます。

永続クラスへマッピングされない「おまけの」ディスクリミネータ値を持つ行が テーブルにあれば、(そのときに限り)force 属性は有効です。 普通はそういうことはありません。

5.1.7. version(オプション)

<version> 要素はオプションで、 テーブルがバージョン・データを含むことを示します。 これは ロング・トランザクション を使うつもりなら、特に役立ちます(以下を見てください)。

<version
        column="version_column"                            (1)
        name="propertyName"                                (2)
        type="typename"                                    (3)
        access="field|property|ClassName"                  (4)
        unsaved-value="null|negative|undefined"            (5)
/>
(1)

column(オプション - デフォルトはプロパティ名): バージョン番号を保持するカラムの名前。

(2)

name:永続クラスのプロパティの名前。

(3)

type (オプション - デフォルトは integer):バージョン番号の型。

(4)

access (オプション - デフォルトは property): プロパティの値へのアクセスにHibernateが使う戦略。

(5)

unsaved-value (オプション - デフォルトは undefined): インスタンスが新しくインスタンス化されたことを示す (セーブされていないことを示す)バージョン・プロパティの値。 以前のSessionでセーブまたはロードされていない一時的なインスタンスと区別するために 使います。 (undefined は識別子プロパティの値が使われることを指定します。)

バージョン番号は long, integer, short, timestamp, calendar 型のいずれかです。

5.1.8. timestamp(オプション)

オプションの <timestamp> 要素は、 テーブルがタイムスタンプ・データを含むことを示します。 これはバージョン付けの代わりの方法として用意されています。 タイムスタンプはもともと楽観的ロックの安全性の低い実装です。 しかしアプリケーションは異なる用途で使うかもしれません。

<timestamp
        column="timestamp_column"           (1)
        name="propertyName"                 (2)
        access="field|property|ClassName"   (3)
        unsaved-value="null|undefined"      (4)
/>
(1)

column(オプション - デフォルトはプロパティ名): タイムスタンプを保持するカラムの名前。

(2)

name: 永続クラスのJava型 Date または Timestamp のJavaBeansスタイル・プロパティの名前。

(3)

access (オプション - デフォルトは property): プロパティの値へのアクセスにHibernateが使う戦略。

(4)

unsaved-value (オプション - デフォルトは null): インスタンスが新しくインスタンス化された (セーブされていない)ことを示すバージョン・プロパティの値。 以前のSessionでセーブまたはロードされた一時的なインスタンスと 区別するために使われます。 (undefined と指定すると、 識別子プロパティの値が使われます。)

<timestamp><version type="timestamp"> と等価であることに注意してください。

5.1.9. property

<property> 要素は、クラスのJavaBeanスタイルの永続プロパティを定義します。

<property
        name="propertyName"                 (1)
        column="column_name"                (2)
        type="typename"                     (3)
        update="true|false"                 (4)
        insert="true|false"                 (4)
        formula="arbitrary SQL expression"  (5)
        access="field|property|ClassName"   (6)
/>
(1)

name:小文字で始まるプロパティ名。

(2)

column(オプション - デフォルトはプロパティ名): マッピングされたデータベース・テーブル・カラムの名前。

(3)

type(オプション):Hibernate型を示す名前。

(4)

update, insert (オプション - デフォルトは true):マッピングされたカラムがSQL UPDATEINSERT に含まれることを指定します。 両方とも false に設定すると、 同じカラムにマッピングされた他のプロパティやトリガや 他のアプリケーションによって初期化された純粋な「導出」プロパティが可能になります。

(5)

formula(オプション): 計算 プロパティのための値を定義するSQL式。 計算プロパティには自分のカラム・マッピングがありません。

(6)

access(オプション - デフォルトは property): プロパティの値へのアクセスにHibernateが使う戦略。

typename には以下のようなものが可能です:

  1. Hibernateの基本型の名前(例 integer, string, character, date, timestamp, float, binary, serializable, object, blob)。

  2. デフォルトの基本型のJavaクラス名 (例 int, float, char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob)。

  3. PersistentEnum のサブクラスの名前(例 eg.Color)。

  4. シリアライズ可能なJavaクラスの名前。

  5. カスタム型のクラス名(例 com.illflow.type.MyCustomType)。

型を指定しなければ、Hibernateは正しいHibernate型を推測するために、 名前付きのプロパティに対してリフレクションを使います。 Hibernateはルール2, 3, 4をその順序に使い、 getterプロパティの返り値のクラスの名前を解釈しようとします。 しかしこれは常に十分とは限りません。 type 属性が必要な場合があります。 (例えば Hibernate.DATEHibernate.TIMESTAMP を区別するとき、 またはカスタム型を指定するとき。)

access 属性で、 実行時にHibernateがどのようにプロパティにアクセスするかを制御できます。 デフォルトではHibernateはプロパティのget/setのペアをコールします。 access="field" と指定すれば、 Hibernateはリフレクションを使いget/setのペアを介さずに、直接フィールドにアクセスします。 インターフェイス net.sf.hibernate.property.PropertyAccessor を 実装するクラスを指定することで、プロパティへのアクセスに独自の戦略を指定することができます。

5.1.10. many-to-one

他の永続クラスへの普通の関連は many-to-one 要素を使い定義します。 リレーショナル・モデルはmany-to-one関連です。 (本当は単なるオブジェクト参照です。)

<many-to-one
        name="propertyName"                                (1)
        column="column_name"                               (2)
        class="ClassName"                                  (3)
        cascade="all|none|save-update|delete"              (4)
        outer-join="true|false|auto"                       (5)
        update="true|false"                                (6)
        insert="true|false"                                (6)
        property-ref="propertyNameFromAssociatedClass"     (7)
        access="field|property|ClassName"                  (8)
		unique="true|false"                                      (9)
/>
(1)

name:プロパティ名。

(2)

column(オプション):カラム名。

(3)

class(オプション - デフォルトは、 リフレクションにより決定されるプロパティの型):関連クラスの名前。

(4)

cascade(オプション): 親オブジェクトから関連オブジェクトへ、どの操作をカスケードさせるかを指定します。

(5)

outer-join(オプション - デフォルトは auto): hibernate.use_outer_join が設定されたとき、 この関連へのアウター・ジョインによるフェッチを可能にします。

(6)

update, insert(オプション - デフォルトは true): マッピングされたカラムがSQL UPDATEINSERT 文に含まれることを指定します。 両方とも false に設定すると、 同じカラムをマッピングされた他のプロパティやトリガや 他のアプリケーションによって初期化された純粋な「導出」プロパティが可能になります。

(7)

property-ref(オプション): この外部キーに結合された関連クラスのプロパティ名。 指定されなければ、関連クラスの主キーが使われます。

(8)

access(オプション - デフォルトは property): プロパティの値へのアクセスにHibernateが使う戦略。

(9)

unique(オプション): DDLの生成において、外部キーカラムに対するユニーク制約を可能にします。

cascade 属性は以下の値を受け付けます: all, save-update, delete, nonenone 以外の値を設定すると、ある操作が関連(子)オブジェクトへ伝播されます。 以下の「ライフサイクル・オブジェクト」を見てください。

outer-join 属性は、異なる3つの値を受け付けます:

  • auto(デフォルト):関連クラスのプロキシがなければ、 アウター・ジョインで関連をフェッチします。

  • true 常にアウター・ジョインで関連をフェッチします。

  • false アウター・ジョインでは関連をフェッチしません。

典型的な many-to-one の定義は、以下のように単純です。

<many-to-one name="product" class="Product" column="PRODUCT_ID"/>

property-ref 属性はレガシー・データのマッピングだけに使われます。 そのレガシー・データでは、外部キーは主キーではない関連テーブルのユニーク・キーを参照します。 これは醜いリレーショナル・モデルです。 例えば Product クラスが、 主キーでないユニークなシリアル・ナンバーを持っていると仮定してみてください。 (unique 属性はSchemaExportツールを使ったHibernateのDDL生成を制御します。)

<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>

以下のように OrderItem のマッピングを使えます:

<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>

しかしこれは決しておすすめできません。

5.1.11. one-to-one

他の永続クラスへのone-to-one関連は、one-to-one 要素で定義します。

<one-to-one
        name="propertyName"                                (1)
        class="ClassName"                                  (2)
        cascade="all|none|save-update|delete"              (3)
        constrained="true|false"                           (4)
        outer-join="true|false|auto"                       (5)
        property-ref="propertyNameFromAssociatedClass"     (6)
        access="field|property|ClassName"                  (7)
/>
(1)

name:プロパティ名。

(2)

class(オプション - デフォルトはリフレクションにより決定されるプロパティの型): 関連クラスの名前。

(3)

cascade(オプション): 親オブジェクトから関連オブジェクトへ、どの操作をカスケードするかを指定します。

(4)

constrained(オプション): マッピングされたテーブルの主キーに対する外部キー制約が、 関連クラスのテーブルを参照することを指定します。 このオプションは save()delete() がカスケードされる順序に影響します (そしてスキーマ・エクスポート・ツールにも使われます)。

(5)

outer-join(オプション - デフォルトは auto): hibernate.use_outer_join が設定されたとき、 この関連に対するアウター・ジョインによるフェッチを有効にします。

(6)

property-ref (オプション):このクラスの主キーに結合された、関連クラスのプロパティ名。 指定されなければ、関連クラスの主キーが使われます。

(7)

access(オプション - デフォルトは property): プロパティの値へのアクセスにHibernateが使う戦略。

one-to-one関連には2種類あります:

  • 主キー関連

  • ユニーク外部キー関連

主キー関連には、余分なテーブル・カラムは必要ありません。 もし2つの行が関連により関係していれば、2つのテーブルは同じ主キーの値を共有します。 そのため2つのオブジェクトを1つの主キーに関係付けたければ、 同じ識別子の値を代入することを確実にしなければいけません。

主キー関連には、以下のマッピングを EmployeePerson にそれぞれ追加してください。

<one-to-one name="person" class="Person"/>
<one-to-one name="employee" class="Employee" constrained="true"/>

今PERSONとEMPLOYEEの関係する行の主キーが同じであることを確実にしなければいけません。 私たちは foreign という、特別なHibernate識別子生成戦略を使います:

<class name="person" table="PERSON">
    <id name="id" column="PERSON_ID">
        <generator class="foreign">
            <param name="property">employee</param>
        </generator>
    </id>
    ...
    <one-to-one name="employee"
        class="Employee"
        constrained="true"/>
</class>

Employee インスタンスが、Personemployee プロパティで参照されるように、 新しくセーブされた Person のインスタンスには同じ主キーの値が代入されます。

もう1つの方法として、Employee から Person への ユニーク制約を使った外部キーは以下のように表現されます:

<many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/>

そしてこの関連は、以下の記述を Person のマッピングに追加すると双方向にできます:

<one-to-one name"employee" class="Employee" property-ref="person"/>

5.1.12. component, dynamic-component

<component> 要素は、 子オブジェクトのプロパティを親クラスのテーブルのカラムへマッピングします。 コンポーネントは順番に、自分のプロパティ、コンポーネント、コレクションを定義できます。 以下の「コンポーネント」を見てください。

<component 
        name="propertyName"                 (1)
        class="className"                   (2)
        insert="true|false"                 (3)
        upate="true|false"                  (4)
        access="field|property|ClassName">  (5)
        
        <property ...../>
        <many-to-one .... />
        ........
</component>
(1)

name:プロパティ名。

(2)

class(オプション - デフォルトはリフレクションにより決定されるプロパティの型): コンポーネント(子)クラスの名前。

(3)

insert:マッピングされたカラムをSQL INSERT に現れるようにするか?

(4)

update: マッピングされたカラムがSQL UPDATE に現れるようにするか?

(5)

access (オプション - デフォルトは property): プロパティの値へのアクセスにHibernateが使う戦略。

子の <property> タグで、子のクラスのプロパティをテーブル・カラムにマッピングします。

<component> 要素は、親エンティティへ戻る参照として、 コンポーネントのクラスのプロパティをマッピングする <parent> サブ要素を受け入れます。

<dynamic-component> 要素は、 1つの Map がコンポーネントとしてマッピングされることを可能にします。 そこではプロパティ名はmapのキーを参照します。

5.1.13. subclass

最後にポリモーフィックな永続化には、ルートの永続クラスの各サブクラスの定義が必要です。 (おすすめの)table-per-class-hierarchyマッピング戦略では、 <subclass> 定義が使われます。

<subclass
        name="ClassName"                              (1)
        discriminator-value="discriminator_value"     (2)
        proxy="ProxyInterface"                        (3)
        lazy="true|false"                             (4)
        dynamic-update="true|false"
        dynamic-insert="true|false">

        <property .... />
        .....
</subclass>
(1)

name:サブクラスの完全修飾されたクラス名。

(2)

discriminator-value(オプション - デフォルトはクラス名): 個々のサブクラスを区別するための値。

(3)

proxy(オプション):lazy初期化プロキシに使うクラスやインターフェイスを指定します。

(4)

lazy(オプション): lazy="true" と設定することは、 プロキシ インターフェイスとしてクラス名を指定することと同じです。

各サブクラスでは、永続プロパティとサブクラスを定義します。 <version><id> プロパティは、 ルートクラスから継承されると仮定されます。 階層構造におけるサブクラスは、ユニークな discriminator-value を定義しなければいけません。 noneが指定されると完全修飾されたJavaクラス名が使われます。

5.1.14. joined-subclass

もう1つの方法として、自分のテーブルに永続化されるサブクラスは、 <joined-subclass> 要素で定義します。 (table-per-subclassマッピング戦略)。

<joined-subclass
        name="ClassName"                    (1)
        proxy="ProxyInterface"              (2)
        lazy="true|false"                   (3)
        dynamic-update="true|false"
        dynamic-insert="true|false">

        <key .... >

        <property .... />
        .....
</joined-subclass>
(1)

name:サブクラスの完全修飾されたクラス名。

(2)

proxy(オプション):lazy初期化プロキシに使うクラスやインターフェイスを指定します。

(3)

lazy(オプション):lazy="true" と設定することは、 プロキシ インターフェイスとしてクラス名を指定することと同じです。

このマッピング戦略には、ディスクリミネータ・カラムは必要ありません。 しかし各サブクラスは <key> 要素を使い、 オブジェクト識別子を保持するテーブル・カラムを定義しなければいけません。 この章の初めのマッピングは以下のように書き直せます:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping package="eg">

        <class name="Cat" table="CATS">
                <id name="id" column="uid" type="long">
                        <generator class="hilo"/>
                </id>
                <property name="birthdate" type="date"/>
                <property name="color" not-null="true"/>
                <property name="sex" not-null="true"/>
                <property name="weight"/>
                <many-to-one name="mate"/>
                <set name="kittens">
                        <key column="MOTHER"/>
                        <one-to-many class="Cat"/>
                </set>
                <joined-subclass name="DomesticCat" table="DOMESTIC_CATS">
                	<key column="CAT"/>
                        <property name="name" type="string"/>
                </joined-subclass>
        </class>

        <class name="eg.Dog">
                <!-- ここにDog用のマッピングを書きます -->
        </class>

</hibernate-mapping>

5.1.15. map, set, list, bag

コレクションについては後で述べます。

5.1.16. import

アプリケーションに同じ名前の2つの永続クラスがあるとします。 そしてHibernateクエリで完全修飾された(パッケージの)名前を指定したくないとします。 そのような場合は auto-import="true" に頼らず、 クラスが「インポート」されたものであると明示することができます。 明示的にマッピングされていないクラスやインターフェイスでさえもインポートできます。

<import class="java.lang.Object" rename="Universe"/>
<import
        class="ClassName"              (1)
        rename="ShortName"             (2)
/>
(1)

class:Javaクラスの完全修飾されたクラス名。

(2)

rename(オプション - デフォルトは修飾されていないクラス名): クエリ言語で使われる名前。

5.2. Hibernate型

5.2.1. エンティティとバリュー

永続サービスに関わるいろいろなJava言語レベルのオブジェクトの振る舞いを理解するためには、 それらを2つのグループに分ける必要があります:

エンティティ はエンティティへの参照を保持する、 他のすべてのオブジェクトから独立して存在します。 参照されないオブジェクトはガベージ・コレクトされてしまう通常のJavaモデルと、 これを比べてみてください。 (親エンティティから子へ、セーブと削除が カスケード されうることを除いて) エンティティは明示的にセーブまたは削除されなければいけません。 これは到達可能性によるオブジェクト永続化のODMGモデルとは異なっています。 大規模なシステムでアプリケーション・オブジェクトが普通どのように使われるかにより緊密に対応します。 エンティティは循環と参照の共有をサポートします。 またそれらはバージョン付けすることもできます。

エンティティの永続状態は他のエンティティや バリュー 型のインスタンスへの参照から構成されます。 バリューはプリミティブ、コレクション、コンポーネント、更新不能オブジェクトです。 エンティティとは違い、(特定のコレクションとコンポーネントにおいて)バリューは、 到達可能性による永続化や削除が 行われます 。 バリュー・オブジェクト(とプリミティブ)は、含んでいるエンティティと一緒に永続化や削除が行われるので、 それらを独立にバージョン付けすることはできません。 バリューには独立したアイデンティティがないので、複数のエンティティやコレクションがこれを共有することはできません。

コレクションを除くすべてのHibernate型が、nullをサポートします。

これまで「永続クラス」という言葉をエンティティの意味で使ってきました。 これからもそうしていきます。 厳密に言うと、永続状態を持つユーザ定義のクラスのすべてが エンティティというわけではありません。 コンポーネント はバリューの意味を持つユーザ定義クラスです。

5.2.2. 基本のバリュー型

基本型 は大まかに以下のように分けられます。

integer, long, short, float, double, character, byte, boolean, yes_no, true_false

Javaプリミティブやラッパークラスから適切な(ベンダー固有の)SQLカラム型への型マッピング。 boolean, yes_notrue_false は、 すべてJavaの boolean または java.lang.Boolean の代替エンコードです。

string

java.lang.String から VARCHAR (またはOracleの VARCHAR2)への型マッピング。

date, time, timestamp

java.util.Date とそのサブクラスからSQL型 DATE, TIME, TIMESTAMP (またはそれらと等価なもの)への型マッピング。

calendar, calendar_date

java.util.Calendar からSQL型 TIMESTAMP, DATE (またはそれらと等価なもの)への型マッピング。

big_decimal

java.math.BigDecimal から NUMERIC (またはOracleの NUMBER)への型マッピング。

locale, timezone, currency

java.util.Locale, java.util.TimeZone, java.util.Currency から VARCHAR (またはOracleの VARCHAR2)への型マッピング。 LocaleCurrency のインスタンスは、 それらのISOコードにマッピングされます。 TimeZone のインスタンスは、それらの ID にマッピングされます。

class

java.lang.Class から VARCHAR (またはOracleの VARCHAR2)への型マッピング。 Class はその完全修飾された名前にマッピングされます。

binary

バイト配列は、適切なSQLバイナリ型にマッピングされます。

text

長いJava文字列は、SQL CLOB または TEXT 型にマッピングされます。

serializable

シリアライズ可能なJava型は、適切なSQLバイナリ型にマッピングされます。 デフォルトで基本型や PersistentEnum を実装しないシリアライズ可能なJavaクラスや インターフェイスの名前を指定することで、 Hibernate型を serializable とすることもできます。

clob, blob

JDBCクラス java.sql.Clobjava.sql.Blob に対する型マッピング。 blobやclobオブジェクトはトランザクションの外では再利用できないため、 アプリケーションによっては不便かもしれません。 (さらにはドライバ・サポートがパッチで首尾一貫していません。)

エンティティとコレクションのユニークな識別子は、binary, blob, clob を除く、どんな基本型でも構いません。 (複合識別子でも構いません。以下を見てください。)

基本のバリュー型にはそれぞれ、net.sf.hibernate.Hibernate で定義された Type 定数があります。 例えば、Hibernate.STRINGstring 型を表現しています。

5.2.3. 永続列挙型

列挙 型は、クラスが更新不能インスタンスの(小さな)定数を持つという、 Javaに共通のイディオムです。 net.sf.hibernate.PersistentEnum を実装し、 操作 toInt()fromInt() を定義することで、 永続列挙型を生成できます:

package eg;
import net.sf.hibernate.PersistentEnum;

public class Color implements PersistentEnum {
    private final int code;
    private Color(int code) {
        this.code = code;
    }
    public static final Color TABBY = new Color(0);
    public static final Color GINGER = new Color(1);
    public static final Color BLACK = new Color(2);

    public int toInt() { return code; }

    public static Color fromInt(int code) {
        switch (code) {
            case 0: return TABBY;
            case 1: return GINGER;
            case 2: return BLACK;
            default: throw new RuntimeException("Unknown color code");
        }
    }
}

Hibernate型の名前は、単に列挙クラスの名前です。 この例では eg.Color です。

5.2.4. カスタム・バリュー型

開発者が独自のバリュー型を作成することは、比較的簡単です。 例えば、java.lang.BigInteger 型のプロパティから VARCHAR カラムへ永続化したいかもしれません。 Hibernateはこのための組み込み型を用意していません。 しかしカスタム型を使えば、プロパティ(またはコレクションの要素)を1つのテーブル・カラムにマッピングできます。 例えば、java.lang.String 型の getName() / setName() プロパティを FIRST_NAME, INITIAL, SURNAME カラムにマッピングできます。

カスタム型を実装するには、net.sf.hibernate.UserType または net.sf.hibernate.CompositeUserType を実装し、 型の完全修飾された名前を使ってプロパティを定義します。 net.sf.hibernate.test.DoubleStringType を確認してください。

<property name="twoStrings" type="net.sf.hibernate.test.DoubleStringType">
    <column name="first_string"/>
    <column name="second_string"/>
</property>

<column> タグで、プロパティを複数のカラムへマッピングできることに注目してください。

Hibernateには多様な組み込み型とコンポーネントのサポートがあるので、 カスタム型を使う必要はめったに ありません。 しかしそれでも、アプリケーション内で頻繁に発生する(エンティティでない) クラスのためにカスタム型を使うのは良いことだと考えられます。 例えば MonetoryAmount(お金)クラスは、 コンポーネントとしてマッピングすることも簡単ですが、 CompositeUserType を使うと良いと考えられます。 その動機の1つは抽象化です。 カスタム型を使えば、お金の値をどのように表現しようとも、 マッピング・ドキュメントは起こりうる変化に対して影響を抑えることができます。

5.2.5. Any型のマッピング

プロパティ・マッピングはさらにもう1つあります。 <any> マッピング要素は、 複数のテーブルからクラスへのポリモーフィックな関連を定義します。 この型のマッピングには必ず複数のカラムが必要です。1番目のカラムは関連エンティティの型を保持します。 残りのカラムは識別子を保持します。この種類の関連には外部キー制約を指定することはできません。 そのためこれは最も使われることのない(ポリモーフィックな)関連のマッピング方法です。 非常に特別な場合に(例えば、検査ログやユーザ・セッション・データなど)これを使うべきです。

<any name="anyEntity" id-type="long" meta-type="eg.custom.Class2TablenameType">
    <column name="table_name"/>
    <column name="id"/>
</any>

meta-type 属性は、アプリケーションがあるカスタム型を指定するために使います。 そのカスタム型はデータベース・カラムの値を id-type で指定された型の識別子プロパティを持つ永続クラスへマッピングするものです。 meta-typeが java.lang.Class のインスタンスを返すなら、他には何も必要ありません。 そうではなく stringcharacter のような基本型なら、 値からクラスへのマッピングを指定しなければいけません。

<any name="anyEntity" id-type="long" meta-type="string">
    <meta-value value="TBL_ANIMAL" class="Animal"/>
    <meta-value value="TBL_HUMAN" class="Human"/>
    <meta-value value="TBL_ALIEN" class="Alien"/>
    <column name="table_name"/>
    <column name="id"/>
</any>
<any
        name="propertyName"                      (1)
        id-type="idtypename"                     (2)
        meta-type="metatypename"                 (3)
        cascade="none|all|save-update"           (4)
        access="field|property|ClassName"        (5)
>
        <meta-value ... />
        <meta-value ... />
        .....
        <column .... />
        <column .... />
        .....
</any>
(1)

name:プロパティ名。

(2)

id-type:識別子の型。

(3)

meta-type(オプション - デフォルトは class): java.lang.Class を1つのデータベース・カラム、 またはディスクリミネータ・マッピングで許された型にマッピングする型。

(4)

cascade(オプション - デフォルトは none): カスケードのスタイル。

(5)

access(オプション - デフォルトは property): プロパティの値へのアクセスにHibernateが使う戦略。

Hibernate1.2で同じような役割を果たしていた、古い object 型はまだサポートされてはいますが、 現在は準非推奨になっています。

5.3. SQL引用識別子

マッピング・ドキュメントでテーブルやカラムの名前をバック・クォートで囲むことで、 Hibernateに生成されるSQL中の識別子を引用させることができます。 HibernateはSQL Dialect に対応する、正しい引用スタイルを使います (普通はダブル・クォートですが、SQL Serverではかぎ括弧、MySQLではバック・クォートです)。

<class name="LineItem" table="`Line Item`">
    <id name="id" column="`Item Id`"/><generator class="assigned"/></id>
    <property name="itemNumber" column="`Item #`"/>
    ...
</class>

5.4. モジュール式マッピング・ファイル

分割されたマッピング・ドキュメントで、hibernate-mapping の直下に subclassjoined-subclass を指定できます。 これにより単に新しいマッピング・ファイルを追加して、クラスの階層構造を拡張することができます。 そのためにはサブクラスのマッピングの extends 属性で、 以前にマッピングされたスーパークラスの名前を指定しなければいけません。 この機能を使うとマッピング・ドキュメントの並び順が重要になります。

<hibernate-mapping>
        <subclass name="eg.subclass.DomesticCat" extends="eg.Cat" discriminator-value="D">
             <property name="name" type="string"/>
        </subclass>
</hibernate-mapping>