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

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

8.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 は永続化属性に対してgetter、 setterメソッドを定義しますが、インターフェイスや識別子プロパティを定義する必要が ないことに注意して下さい。

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

<class name="eg.Person" table="person">
    <id name="Key" column="pid" type="string">
        <generator class="uuid"/>
    </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テーブルは pidbirthdayinitialfirstlast カラムを持ちます。

全ての値型のように、コンポーネントは参照の共有をすることができません。 言い換えると、二人のPersonは同じ名前を持つことができますが、二つのPersonオブジェクトは "値が同じだけ"の別々のnameオブジェクトを含んでいるということです。 コンポーネントの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"/>
    </id>
    <property name="birthday" type="date"/>
    <component name="Name" class="eg.Name" unique="true">
        <parent name="namedPerson"/> <!-- reference back to the Person -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </component>
</class>

8.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> 属性を持つ コンポジットエレメントがあります。 このマッピングは、コンポジットエレメントクラスを多対多関連テーブルの 余分なカラムへマッピングします。 次の例は Order から、Item への多対多関連です。 purchaseDatepricequantity は関連の属性となります。

<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 attribute is optional -->
        </composite-element>
    </set>
</class>

もちろん、双方向関連のナビゲーションのために反対側からpurchaseへの参照を作ることは出来ません。 コンポーネントは値型であり、参照を共有できないことを覚えておいてください。 一つの Purchase は一つの Order のsetに存在できますが、 同時に Item から参照することは出来ません。

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>

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

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

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

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

コンポーネントをエンティティクラスの識別子として使うことができます。 コンポーネントクラスは以下の条件を満たす必要があります。

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

  • データベース上の複合キーの等価性と矛盾のないように、equals()hashCode() を再実装しなければなりません。

注意: Hibernate3において、2番目の条件は絶対的な条件ではありません。 しかしとにかく条件を満たしてください。

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

通常の <id> 宣言の代わりに <composite-id> タグを (ネストされた <key-property> 属性と共に)使います。 以下の例では、OrderLine クラスは Order の(複合)主キーに 依存した主キーを持っています。

<class name="OrderLine">
    
    <composite-id name="id" class="OrderLineId">
        <key-property name="lineId"/>
        <key-property name="orderId"/>
        <key-property name="customerId"/>
    </composite-id>
    
    <property name="name"/>
    
    <many-to-one name="order" class="Order"
            insert="false" update="false">
        <column name="orderId"/>
        <column name="customerId"/>
    </many-to-one>
    ....
    
</class>

このとき、OrderLine テーブルへ関連する外部キーもまた複合です。 他のクラスのマッピングでこれを宣言しなければなりません。 OrderLine への関連は次のようにマッピングされます。

<many-to-one name="orderLine" class="OrderLine">
<!-- the "class" attribute is optional, as usual -->
    <column name="lineId"/>
    <column name="orderId"/>
    <column name="customerId"/>
</many-to-one>

<column> タグはどこであっても column 属性の 代わりになります。)

OrderLine への many-to-many 関連も 複合外部キーを使います。

<set name="undeliveredOrderLines">
    <key column name="warehouseId"/>
    <many-to-many class="OrderLine">
        <column name="lineId"/>
        <column name="orderId"/>
        <column name="customerId"/>
    </many-to-many>
</set>

Order にある OrderLine のコレクションは 次のものを使用します。

<set name="orderLines" inverse="true">
    <key>
        <column name="orderId"/>
        <column name="customerId"/>
    </key>
    <one-to-many class="OrderLine"/>
</set>

<one-to-many> 属性は、例によってカラムを宣言しません)

OrderLine 自身がコレクションを持っている場合、 同時に複合外部キーも持っています。

<class name="OrderLine">
    ....
    ....
    <list name="deliveryAttempts">
        <key>   <!-- a collection inherits the composite key type -->
            <column name="lineId"/>
            <column name="orderId"/>
            <column name="customerId"/>
        </key>
        <list-index column="attemptId" base="1"/>
        <composite-element class="DeliveryAttempt">
            ...
        </composite-element>
    </set>
</class>

8.5. 動的コンポーネント

Map 型のプロパティのマッピングも可能です。

<dynamic-component name="userAttributes">
    <property name="foo" column="FOO" type="string"/>
    <property name="bar" column="BAR" type="integer"/>
    <many-to-one name="baz" class="Baz" column="BAZ_ID"/>
</dynamic-component>

<dynamic-component> マッピングのセマンティクスは <component> と同一のものです。この種のマッピングの利点は、マッピングドキュメントの編集により、配置時にbeanの属性を 決定できる点です。また、DOMパーサを利用して、マッピングドキュメントのランタイム操作が可能です。 さらに、Configuration オブジェクト経由でHibernateのコンフィグレーション時のメタモデルに アクセス(または変更)が可能です。