第7章 コンポーネントのマッピング

コンポーネントは、Hibernateを通して様々な状況の中で異なる目的のために再利用されます。

7.1. 依存オブジェクト

コンポーネントはエンティティではなくバリュー・タイプとして永続化された、包含されたオブジェクトです。つまりコンポーネントはエンティティではなく、バリュー・タイプです。コンポーネントという言葉はコンポジションというオブジェクト指向の概念を参照してください(アーキテクチャレベルのコンポーネントではありません)。例えば以下のようなPersonをモデルとします。:

public class Person {
    private java.util.Date birthday;
    private Name name;
    private String key;
    public String getKey() {
        return key;
    }
    private void setKey(String key) {
        this.key=key;
    }
    public java.util.Date getBirthday() {
        return birthday;
    }
    public void setBirthday(java.util.Date birthday) {
        this.birthday = birthday;
    }
    public Name getName() {
        return name;
    }
    public void setName(Name name) {
        this.name = name;
    }
    ......
    ......
}
public class Name {
    char initial;
    String first;
    String last;
    public String getFirst() {
        return first;
    }
    void setFirst(String first) {
        this.first = first;
    }
    public String getLast() {
        return last;
    }
    void setLast(String last) {
        this.last = last;
    }
    public char getInitial() {
        return initial;
    }
    void setInitial(char initial) {
        this.initial = initial;
    }
}

いまNamePersonのコンポーネントとして永続化することができます。ここでNameの永続化属性に対してゲッター、セッター・メソッドを定義しますが、インターフェースや識別子フィールドを宣言する必要がないことに注意してください

マッピング定義は以下のようになります。

<class name="eg.Person" table="person">
    <id name="Key" column="pid" type="string">
        <generator class="uuid.hex"/>
    </id>
    <property name="birthday" type="date"/>
    <component name="Name" class="eg.Name"> <!-- class attribute optional -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </component>
</class>

personテーブルはpid, birthday, initial, first , lastを持ちます。

すべてのバリュー・タイプのように、コンポーネントの参照は共有されません。コンポーネントのnull値におけるセマンティクスはアドホックです。含んでいるオブジェクトを再読み込みする場合、Hibernateはすべてのコンポーネントのカラムがnullである、すなわちコンポーネント自体がnullであると想定します。この事は大抵の場合問題ありません。

コンポーネントのプロパティはどんなHibernateの型でも構いません。(コレクション、many-to-one関連、他のコンポーネントなど)。ネストされたコンポーネントをめったに使わない使用法と考えるべきではありません。Hibernateは非常にきめの細かいオブジェクト・モデルをサポートするように意図されています。

<component>要素は親エンティティへの逆参照として、コンポーネント・クラスの属性をマッピングする<parent>サブ要素を使用できます。

<class name="eg.Person" table="person">
    <id name="Key" column="pid" type="string">
        <generator class="uuid.hex"/>
    </id>
    <property name="birthday" type="date"/>
    <component name="Name" class="eg.Name">
        <parent name="namedPerson"/> <!-- reference back to the Person -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </component>
</class>

7.2. 依存オブジェクトのコレクション

Hibernateはコンポーネントのコレクションをサポートしています(例えばName型の配列)。<element>タグの代わりに<composite-element>タグを使ってコンポーネントのコレクションを宣言してください。:

<set name="someNames" table="some_names" lazy="true">
    <key column="id"/>
    <composite-element class="eg.Name"> <!-- class attribute required -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </composite-element>
</set>

注意:コンポジット・エレメントのSetを定義したならばequals()hashCode()を正しく実装することは重要です。

コンポジット・エレメントはコレクションではなく、コンポーネントを含んでいることもあります。コンポジット・エレメント自身がコンポーネントを含んでいる場合は<nested-composite-element>タグを使用してください。自身がコンポーネントを持っているコンポーネントのコレクション、といったケースはめったにありません。この段階までにone-to-many関連の方がより適切なのではないかと考えてください。エンティティとしてコンポジット・エレメントを再モデリングしてみてください。しかしJavaモデルは同じですが、リレーショナル・モデルと永続動作はまだわずかに異なることに注意してください。

もし<set>を使用するなら、コンポジット・エレメントのマッピングがnullを持つ属性をサポートしていないことに注意してください。Hibernateはオブジェクトを削除するときに、レコードを識別するために各々のカラムの値を使用する必要があるので、null値を持てません(コンポジット・エレメントのテーブルには別の主キーカラムはありません)。not-nullの属性をコンポジット・エレメントで使用するか、または<list>, <map>, <bag>, <idbag>を選択するかしなくてはなりません。

コンポジット・エレメントの特別なケースは入れ子の<many-to-one> 要素を持ったコンポジット・エレメントです。このようなマッピングはコンポジット・エレメント・クラスにmany-to-many関連テーブルの余分なカラムをマッピングすることを可能にします。次はOrderから、purchaseDatepricequantityが関連からの属性であるItemへのmany-to-many関連の例です。

<class name="eg.Order" .... >
    ....
    <set name="purchasedItems" table="purchase_items" lazy="true">
        <key column="order_id">
        <composite-element class="eg.Purchase">
            <property name="purchaseDate"/>
            <property name="price"/>
            <property name="quantity"/>
            <many-to-one name="item" class="eg.Item"/> <!-- class属性はオプションです -->
        </composite-element>
    </set>
</class>

3項関連(あるいは4項、それ以上)さえ可能です:

<class name="eg.Order" .... >
    ....
    <set name="purchasedItems" table="purchase_items" lazy="true">
        <key column="order_id">
        <composite-element class="eg.OrderLine">
            <many-to-one name="purchaseDetails class="eg.Purchase"/>
            <many-to-one name="item" class="eg.Item"/>
        </composite-element>
    </set>
</class>

コンポジット・エレメントは他のエンティティへの関連として同じシンタックスを使っているクエリ内で使用できます。

7.3. mapのインデックスとしてのコンポーネント

<composite-index>要素はMapのキーとしてコンポーネントのクラスをマッピングします。 必ずマッピングクラス上でhashCode()およびequals()を正確にオーバライドするようにしてください。

7.4. 複合識別子としてのコンポーネント

コンポーネントをエンティティ・クラスの識別子としても使用できます。コンポーネント・クラスは以下のある条件を満たさなければなりません。:

  • java.io.Serializableを実装しなければなりません。

  • データベースの複合キーと同等の概念としてequals()hashCode()を一貫して再実装しなければなりません。

複合キーを生成するためにIdentifierGeneratorを使用することができません。代わりにアプリケーションはそれ自身の識別子を割り当てなくてはなりません。

複合識別子は保存する前にオブジェクトに割り当てられなくてはならないので、新しく作成されたインスタンスと以前のSessionで保存されたインスタンスを区別するために、識別子のunsaved-valueを使用することができません。

saveOrUpdate()あるいは保存/更新のカスケードを使用したければ、その代りにInterceptor.isUnsaved()を実装できます。代案として、新しい一時的インスタンスを示す値を指定するために<version>要素(または<timestamp>要素)にunsaved-value属性を設定できます。 この場合、エンティティのバージョンは(割り当てられた)識別子の代わりに使用されます。また、Interceptor.isUnsaved()をあなた自身が実装する必要がありません。

複合識別子クラスの宣言に<id>の代わりに<composite-id>タグ(<component>と同じ属性および要素)を使用してください。:

<class name="eg.Foo" table"FOOS">
    <composite-id name="compId" class="eg.FooCompositeID">
        <key-property name="string"/>
        <key-property name="short"/>
        <key-property name="date" column="date_" type="date"/>
    </composite-id>
    <property name="name"/>
    ....
</class>

テーブルFOOSへの外部キーも複合キーです。他のクラス用のマッピング定義の中でこれを宣言しなければなりません。Fooへの関連は以下のように宣言されます。:

<many-to-one name="foo" class="eg.Foo">
<!-- the "class" attribute is optional, as usual -->
    <column name="foo_string"/>
    <column name="foo_short"/>
    <column name="foo_date"/>
</many-to-one>

この新しい<column>タグも多重カラム・カスタム型によって使用されます。実際にどこでもcolumn属性の代わりとなります。Foo型の要素を備えたコレクションは以下のように使用します。:

<set name="foos">
    <key column="owner_id"/>
    <many-to-many class="eg.Foo">
        <column name="foo_string"/>
        <column name="foo_short"/>
        <column name="foo_date"/>
    </many-to-many>
</set>

他方で、通常<one-to-many>はカラムを宣言しません。

Foo自身がコレクションを含んでいる場合、コレクションは複合外部キーを必要とします。

<class name="eg.Foo">
    ....
    ....
    <set name="dates" lazy="true">
        <key>   <!-- a collection inherits the composite key type -->
            <column name="foo_string"/>
            <column name="foo_short"/>
            <column name="foo_date"/>
        </key>
        <element column="foo_date" type="date"/>
    </set>
</class>

7.5. 動的コンポーネント

以下のようにMap型のプロパティのマッピングも可能です:

<dynamic-component name="userAttributes">
    <property name="foo" column="FOO"/>
    <property name="bar" column="BAR"/>
    <many-to-one name="baz" class="eg.Baz" column="BAZ"/>
</dynamic-component>

<dynamic-component>マッピングのセマンティクスは<component>と同じです。この種のマッピングの利点はマッピング文書編集により、配置時にビーンの属性を決定できる点です。(さらに、DOMパーサを使用してマッピングドキュメントを実行時に操作することも可能です)。