Hibernate.orgCommunity Documentation

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

8.1. 依存オブジェクト
8.2. 従属するオブジェクトのコレクション
8.3. Map のインデックスとしてのコンポーネント
8.4. 複合識別子としてのコンポーネント
8.5. 動的コンポーネント

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

コンポーネントは、エンティティの参照ではなく値型として永続化された、包含されたオブジェクトです。「コンポーネント」という言葉については、コンポジションというオブジェクト指向の概念を参照してください(アーキテクチャレベルのコンポーネントではありません)。例えば、以下の 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
>

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
>

コンポジットエレメントはコレクションを含まず、コンポーネントを含むこともあります。コンポジットエレメント自身がコンポーネントを含んでいる場合は <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
>

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

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

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

複合キーを生成するために 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
>

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
>

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 のコンフィグレーション時のメタモデルにアクセス(または変更)が可能です。