Hibernate.orgCommunity Documentation

第6章 コレクションのマッピング

6.1. コレクションの永続化
6.2. コレクションのマッピング
6.2.1. コレクションの外部キー
6.2.2. コレクションの要素
6.2.3. インデックス付きのコレクション
6.2.4. 値のコレクションと多対多関連
6.2.5. 一対多関連
6.3. 高度なコレクションマッピング
6.3.1. ソートされたコレクション
6.3.2. 双方向関連
6.3.3. インデックス付きコレクションと双方向関連
6.3.4. 3項関連
6.3.5. Using an <idbag>
6.4. コレクションの例

コレクション型のフィールドを永続化するには、そのコレクション型がインターフェース型である必要があります。例えば、

public class Product {

    private String serialNumber;
    private Set parts = new HashSet();
    
    public Set getParts() { return parts; }
    void setParts(Set parts) { this.parts = parts; }
    public String getSerialNumber() { return serialNumber; }
    void setSerialNumber(String sn) { serialNumber = sn; }
}

実在するインターフェースには java.util.Setjava.util.Collectionjava.util.Listjava.util.Mapjava.util.SortedSetjava.util.SortedMap などがあります。または、任意のインターフェースが使えます。 (ただし、「任意のインターフェース」を使用する場合は、 org.hibernate.usertype.UserCollectionType の実装クラスを作成する必要があります。)

HashSet のインスタンスを持つインスタンス変数がどのように初期化されるかに注目してみましょう。これは新たに生成された(永続化されていない)コレクション型のプロパティを初期化する最適な方法です。 (例えば persist() により)インスタンスを永続化しようとしたとき、 Hibernate は HashSet を Hibernate 独自の Set の実装クラスに置き換えます。このため、次のようなエラーには注意が必要です。

Cat cat = new DomesticCat();

Cat kitten = new DomesticCat();
....
Set kittens = new HashSet();
kittens.add(kitten);
cat.setKittens(kittens);
session.persist(cat);
kittens = cat.getKittens(); // Okay, kittens collection is a Set
(HashSet) cat.getKittens(); // Error!

Hibernate により注入された永続性コレクションは、インターフェース型に応じて、 HashMapHashSetTreeMapTreeSetArrayList のように振舞います。

コレクションインスタンスは、値型として普通に振舞います。永続化オブジェクトに参照されたときに自動的に永続化され、参照がなくなったときに自動的に削除されます。もしある永続化オブジェクトから別の永続化オブジェクトに渡されたら、その要素は現在のテーブルから別のテーブルに移動するかもしれません。2つのエンティティが同じコレクションインスタンスを共有してはいけません。リレーショナルモデルをベースにしているため、コレクション型のプロパティに null 値を代入しても意味がありません。つまり Hibernate は参照先のないコレクションと空のコレクションを区別しません。

しかしそれほど心配しなくても構いません。普段使っている Java のコレクションと同じように、永続化コレクションを使ってください。双方向関連の意味を理解すればよいのです(これは後ほど説明します)。

コレクションをマッピングするためのマッピング要素は、インターフェースの型に依存します。例えば、 <set> 要素は Set 型のプロパティをマッピングするために使います。

<class name="Product">

    <id name="serialNumber" column="productSerialNumber"/>
    <set name="parts">
        <key column="productSerialNumber" not-null="true"/>
        <one-to-many class="Part"/>
    </set>
</class
>

マッピング要素には <set> の他に <list><map><bag><array><primitive-array> があります。代表として、 <map> 要素を下記に示します。

<map
    name="prop(1)ertyName"
    table="tab(2)le_name"
    schema="sc(3)hema_name"
    lazy="true(4)|extra|false"
    inverse="t(5)rue|false"
    cascade="a(6)ll|none|save-update|delete|all-delete-orphan|delete-orphan"
    sort="unso(7)rted|natural|comparatorClass"
    order-by="(8)column_name asc|desc"
    where="arb(9)itrary sql where condition"
    fetch="joi(10)n|select|subselect"
    batch-size(11)="N"
    access="fi(12)eld|property|ClassName"
    optimistic(13)-lock="true|false"
    mutable="t(14)rue|false"
    node="element-name|."
    embed-xml="true|false"
>

    <key .... />
    <map-key .... />
    <element .... />
</map
>

1

name :コレクション型であるプロパティの名前

2

table (オプション - デフォルトはプロパティ名):コレクションテーブルの名前(一対多関連では使用しません)。

3

schema (オプション):テーブルスキーマの名前。ルート要素で宣言されているスキーマより優先されます。

4

lazy (オプション - デフォルトは true): 遅延フェッチを無効にし、関連を常に即時にフェッチにするために使用します。または、「extra-lazy」フェッチを有効にするために使用します。「extra-lazy」フェッチは、ほとんどの操作ではコレクションを初期化しません (非常に大きなコレクションに適しています)。

5

inverse (オプション - デフォルトは false):このコレクションが双方向関連の「逆」側であるとマークします。

6

cascade (オプション - デフォルトは none):子エンティティへのカスケード操作を有効にします。

7

sort (オプション):コレクションを自然な順序でソートする場合は natural を指定します。あるいは Comparator クラスを指定します。

8

order-by (オプション、 JDK1.4 のみ) MapSet、 bag のイテレーション順序を定義するテーブルカラムを指定すると共に、オプションとして ascdesc を指定します。

9

where (オプション):コレクションの検索や削除の際に使う任意の SQL のWHERE 条件を指定します (利用可能なデータの一部分だけをコレクションが含むべきときに、これは有用です)。

10

fetch (オプション - デフォルトは select):外部結合によるフェッチ、順次選択フェッチ (sequential select fetch) 、順次サブセレクトフェッチ (sequential subselect fetch) のどれかを選択してください。

11

batch-size (オプション - デフォルトは 1):コレクションのインスタンスの遅延フェッチのための「バッチサイズ」を指定します。

12

access (オプション - デフォルトは property):コレクション型プロパティの値にアクセスするために使用する戦略です。

13

optimistic-lock (オプション - デフォルトは true) コレクションの状態を変えることによって、そのオーナーであるエンティティのバージョンがインクリメントされるかを指定します。 (一対多関連では、ほとんどの場合において無効に設定するのが妥当です。)

14

mutable(オプション - デフォルトは truefalse 値は、コレクションの要素が変更されないことを表します (ある場合には、少しパフォーマンスを高めます)。

set と bag を除く全てのコレクションマッピングには、コレクションテーブルの中に インデックス用のカラム が必要です。そのカラムに、配列や List のインデックス、もしくは Map のキーをマッピングします。 Map のインデックスは、 <map-key> によりマッピングされた基本型か、 <map-key-many-to-many> によりマッピングされたエンティティの関連か、あるいは <composite-map-key> によりマッピングされたコンポジット型になります。配列かリストのインデックスは、常に integer 型で、 <list-index> 要素によりマッピングします。マッピングされたカラムにはシーケンシャルな整数を格納します (デフォルトでは0から番号が付けられます)。

<list-index
        column(1)="column_name"
        base="(2)0|1|..."/>

1

column_name (required): the name of the column holding the collection index values.

1

base (optional - defaults to 0): the value of the index column that corresponds to the first element of the list or array.

<map-key
        column(1)="column_name"
        formul(2)a="any SQL expression"
        type="(3)type_name"
        node="@attribute-name"
        length="N"/>

1

column (optional): the name of the column holding the collection index values.

2

formula (optional): a SQL formula used to evaluate the key of the map.

3

type (required): the type of the map keys.

<map-key-many-to-many
        column(1)="column_name"
        formul(2)(3)a="any SQL expression"
        class="ClassName"
/>

1

column (optional): the name of the foreign key column for the collection index values.

2

formula (optional): a SQ formula used to evaluate the foreign key of the map key.

3

class (required): the entity class used as the map key.

If your table does not have an index column, and you still wish to use List as the property type, you can map the property as a Hibernate <bag>. A bag does not retain its order when it is retrieved from the database, but it can be optionally sorted or ordered.

値のコレクションや多対多関連は、専用の コレクションテーブル が必要です。このテーブルは、外部キーカラムと、 コレクション要素のカラム と、場合によってはインデックスカラムを持ちます。

値のコレクションのために、 <element> タグを使用します。

<element
        column(1)="column_name"
        formul(2)a="any SQL expression"
        type="(3)typename"
        length="L"
        precision="P"
        scale="S"
        not-null="true|false"
        unique="true|false"
        node="element-name"
/>

1

column (optional): the name of the column holding the collection element values.

2

formula (optional): an SQL formula used to evaluate the element.

3

type (required): the type of the collection element.

A many-to-many association is specified using the <many-to-many> element.

<many-to-many
        column(1)="column_name"
        formul(2)a="any SQL expression"
        class=(3)"ClassName"
        fetch=(4)"select|join"
        unique(5)="true|false"
        not-fo(6)und="ignore|exception"
        entity(7)-name="EntityName"
        proper(8)ty-ref="propertyNameFromAssociatedClass"
        node="element-name"
        embed-xml="true|false"
    />

1

column (optional): the name of the element foreign key column.

2

formula (optional): an SQL formula used to evaluate the element foreign key value.

3

class (required): the name of the associated class.

4

fetch (optional - defaults to join): enables outer-join or sequential select fetching for this association. This is a special case; for full eager fetching in a single SELECT of an entity and its many-to-many relationships to other entities, you would enable join fetching,not only of the collection itself, but also with this attribute on the <many-to-many> nested element.

5

unique (optional): enables the DDL generation of a unique constraint for the foreign-key column. This makes the association multiplicity effectively one-to-many.

6

not-found (optional - defaults to exception): specifies how foreign keys that reference missing rows will be handled: ignore will treat a missing row as a null association.

7

entity-name (optional): the entity name of the associated class, as an alternative to class.

8

property-ref (optional): the name of a property of the associated class that is joined to this foreign key. If not specified, the primary key of the associated class is used.

Here are some examples.

A set of strings:


<set name="names" table="person_names">
    <key column="person_id"/>
    <element column="person_name" type="string"/>
</set
>

整数値を含む bag (bagは order-by 属性によって反復順序が定義されています):


<bag name="sizes"
        table="item_sizes" 
        order-by="size asc">
    <key column="item_id"/>
    <element column="size" type="integer"/>
</bag
>

エンティティの配列 - この場合、多対多の関連です。


<array name="addresses"
        table="PersonAddress" 
        cascade="persist">
    <key column="personId"/>
    <list-index column="sortOrder"/>
    <many-to-many column="addressId" class="Address"/>
</array
>

文字列と日付の map


<map name="holidays"
        table="holidays" 
        schema="dbo" 
        order-by="hol_name asc">
    <key column="id"/>
    <map-key column="hol_name" type="string"/>
    <element column="hol_date" type="date"/>
</map
>

コンポーネントの list (次の章で詳しく説明します)


<list name="carComponents"
        table="CarComponents">
    <key column="carId"/>
    <list-index column="sortOrder"/>
    <composite-element class="CarComponent">
        <property name="price"/>
        <property name="type"/>
        <property name="serialNumber" column="serialNum"/>
    </composite-element>
</list
>

一対多関連 は、コレクションテーブルを介さず、外部キーにより2つのクラスのテーブルを関連付けます。このマッピングは標準的な Java のコレクションのセマンティクスをいくつか失います:

Product から Part への関連は、 Part テーブルへの外部キーカラムと、場合によってはインデックスカラムが必要です。 <one-to-many> タグは、これが一対多関連であることを表しています。

<one-to-many
        class=(1)"ClassName"
        not-fo(2)und="ignore|exception"
        entity(3)-name="EntityName"
        node="element-name"
        embed-xml="true|false"
    />

1

class (必須): 関連クラスの名前。

2

not-found (オプション - デフォルトは exception): 参照先の行がないキャッシュされた識別子をどのように扱うかを指定します: ignore を指定すると、行がないことを関連がないものとして扱います。

3

entity-name (オプション): class の代替である関連クラスのエンティティ名。 class の代わりに指定する、関連クラスのエンティティ名。

<one-to-many> 要素はカラムを宣言する必要がないことに注意してください。同様に テーブル 名を指定する必要もありません。

次の例は、名称(Part の永続的なプロパティである partName) による Part エンティティの map を表しています。 formula によるインデックスを使っていることに注意してください。


<map name="parts"
        cascade="all">
    <key column="productId" not-null="true"/>
    <map-key formula="partName"/>
    <one-to-many class="Part"/>
</map
>

Hibernate は java.util.SortedMapjava.util.SortedSet を実装したコレクションをサポートしています。開発者はマッピング定義ファイルにコンパレータを指定しなければなりません:


<set name="aliases"
            table="person_aliases" 
            sort="natural">
    <key column="person"/>
    <element column="name" type="string"/>
</set>

<map name="holidays" sort="my.custom.HolidayComparator">
    <key column="year_id"/>
    <map-key column="hol_name" type="string"/>
    <element column="hol_date" type="date"/>
</map
>

sort 属性に設定できる値は unsortednatural および、 java.util.Comparator を実装したクラスの名前です。

ソートされたコレクションは実質的には java.util.TreeSetjava.util.TreeMap のように振舞います。

もしデータベース自身にコレクションの要素を並べさせたいなら、 setbagmaporder-by 属性を使います。この解決法は JDK1.4 、もしくはそれ以上のバージョンで利用可能です (LinkedHashSet または LinkedHashMapを使って実装されています)。整列はメモリ上ではなく、 SQL クエリ内で実行されます。


<set name="aliases" table="person_aliases" order-by="lower(name) asc">
    <key column="person"/>
    <element column="name" type="string"/>
</set>

<map name="holidays" order-by="hol_date, hol_name">
    <key column="year_id"/>
    <map-key column="hol_name" type="string"/>
    <element column="hol_date type="date"/>
</map
>

関連は、コレクションの filter() を使うことで、実行時に任意の criteria によってソートすることも可能です。

sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();

双方向関連 は関連のどちら「側」からでもナビゲーションできます。2種類の双方向関連がサポートされています:

2つの多対多関連で同じデータベーステーブルをマッピングし、片方を inverse として宣言することで、双方向の多対多関連を指定することが出来ます (どちらを inverse に選んだとしても、そちら側にはインデックス付きのコレクションは使えません)。

次に双方向の多対多関連の例を示します。各カテゴリは多数のアイテムを持つことができ、各アイテムは多くのカテゴリに属することが出来ます。


<class name="Category">
    <id name="id" column="CATEGORY_ID"/>
    ...
    <bag name="items" table="CATEGORY_ITEM">
        <key column="CATEGORY_ID"/>
        <many-to-many class="Item" column="ITEM_ID"/>
    </bag>
</class>

<class name="Item">
    <id name="id" column="ITEM_ID"/>
    ...

    <!-- inverse end -->
    <bag name="categories" table="CATEGORY_ITEM" inverse="true">
        <key column="ITEM_ID"/>
        <many-to-many class="Category" column="CATEGORY_ID"/>
    </bag>
</class
>

関連の inverse 側にのみ行われた変更は永続化 されません。これは、 Hibernate は全ての双方向関連について、メモリ上に2つの表現を持っているという意味です。つまり一つは A から B へのリンクで、もう一つは B から A へのリンクということです。 Java のオブジェクトモデルについて考え、 Java で双方向関係をどうやって作るかを考えれば、これは理解しやすいです。下記に、 Java での双方向関連を示します。



category.getItems().add(item);          // The category now "knows" about the relationship
item.getCategories().add(category);     // The item now "knows" about the relationship
session.persist(item);                   // The relationship won't be saved!
session.persist(category);               // The relationship will be saved

関連の inverse ではない側は、メモリ上の表現をデータベースに保存するのに使われます。

双方向の一対多関連を定義するには、一対多関連を多対一関連と同じテーブルのカラムにマッピングし、多側に inverse="true" と宣言します。


<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <set name="children" inverse="true">
        <key column="parent_id"/>
        <one-to-many class="Child"/>
    </set>
</class>

<class name="Child">
    <id name="id" column="child_id"/>
    ....
    <many-to-one name="parent" 
        class="Parent" 
        column="parent_id"
        not-null="true"/>
</class
>

関連の片側に inverse="true" を設定しても、カスケード操作に影響を与えません。これらは直交した概念です。

片側が <list><map> である双方向関連は、特によく考える必要があります。インデックスカラムにマップされる子クラスのプロパティがある場合は、問題ないです。コレクションのマッピングで inverse="true" を使い続けられます。


<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <map name="children" inverse="true">
        <key column="parent_id"/>
        <map-key column="name" 
            type="string"/>
        <one-to-many class="Child"/>
    </map>
</class>

<class name="Child">
    <id name="id" column="child_id"/>
    ....
    <property name="name" 
        not-null="true"/>
    <many-to-one name="parent" 
        class="Parent" 
        column="parent_id"
        not-null="true"/>
</class
>

しかし、子クラスにそのようなプロパティがない場合は、関連を真に双方向であると考えることができません (関連の片側に利用できる情報がありますが、もう一方にはありません)。この場合は、コレクションに inverse="true" をマッピングできません。代わりに、次のようなマッピングが使えます:


<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <map name="children">
        <key column="parent_id"
            not-null="true"/>
        <map-key column="name" 
            type="string"/>
        <one-to-many class="Child"/>
    </map>
</class>

<class name="Child">
    <id name="id" column="child_id"/>
    ....
    <many-to-one name="parent" 
        class="Parent" 
        column="parent_id"
        insert="false"
        update="false"
        not-null="true"/>
</class
>

Note that in this mapping, the collection-valued end of the association is responsible for updates to the foreign key.

The majority of the many-to-many associations and collections of values shown previously all map to tables with composite keys, even though it has been have suggested that entities should have synthetic identifiers (surrogate keys). A pure association table does not seem to benefit much from a surrogate key, although a collection of composite values might. It is for this reason that Hibernate provides a feature that allows you to map many-to-many associations and collections of values to a table with a surrogate key.

bag のセマンティックスを持った List(または Collection)を <idbag> 要素にマッピングできます。


<idbag name="lovers" table="LOVERS">
    <collection-id column="ID" type="long">
        <generator class="sequence"/>
    </collection-id>
    <key column="PERSON1"/>
    <many-to-many column="PERSON2" class="Person" fetch="join"/>
</idbag
>

ご存知のように <idbag> はエンティティクラスのように人工的な id ジェネレータを持っています。異なる代理キーをそれぞれのコレクションの列に割り当てます。しかし、 Hibernate はある行の代理キーの値を見つけ出す機構を持っていません。

<idbag> を更新するパフォーマンスは通常の <bag> よりも良いことに注目してください。 Hibernate は個々の行を効果的に見つけることができ、 list や map 、 set のように個別にその行を更新、削除できます。

現在の実装では、 native という id 生成戦略を <idbag> コレクションの識別子に対して使えません。

This section covers collection examples.

The following class has a collection of Child instances:

package eg;

import java.util.Set;
public class Parent {
    private long id;
    private Set children;
    public long getId() { return id; }
    private void setId(long id) { this.id=id; }
    private Set getChildren() { return children; }
    private void setChildren(Set children) { this.children=children; }
    ....
    ....
}

If each child has, at most, one parent, the most natural mapping is a one-to-many association:


<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children">
            <key column="parent_id"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping
>

これは以下のテーブル定義にマッピングします。


create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255), parent_id bigint )
alter table child add constraint childfk0 (parent_id) references parent

もし parent が 要求 されるなら、双方向の一対多関連を使用してください:


<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children" inverse="true">
            <key column="parent_id"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
        <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
    </class>

</hibernate-mapping
>

NOT NULL 制約に注意してください。


create table parent ( id bigint not null primary key )
create table child ( id bigint not null
                     primary key,
                     name varchar(255),
                     parent_id bigint not null )
alter table child add constraint childfk0 (parent_id) references parent

あるいは、もしこの関連は単方向であるべきと強く主張するのであれば、 <key> マッピングに NOT NULL 制約を宣言できます:


<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children">
            <key column="parent_id" not-null="true"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping
>

一方で、もし child が複数の parent を持てるならば、多対多関連が妥当です:


<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children" table="childset">
            <key column="parent_id"/>
            <many-to-many class="Child" column="child_id"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping
>

テーブル定義は以下のようになります:

create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255) )
create table childset ( parent_id bigint not null,
                        child_id bigint not null,
                        primary key ( parent_id, child_id ) )
alter table childset add constraint childsetfk0 (parent_id) references parent
alter table childset add constraint childsetfk1 (child_id) references child

For more examples and a complete explanation of a parent/child relationship mapping, see 22章例: 親/子供 for more information.

Even more complex association mappings are covered in the next chapter.