Hibernate.orgCommunity Documentation
コレクション型のフィールドを永続化するには、そのコレクション型がインターフェース型である必要があります。例えば、
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.Set、 java.util.Collection、 java.util.List、 java.util.Map、 java.util.SortedSet、 java.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 により注入された永続性コレクションは、インターフェース型に応じて、 HashMap や HashSet、 TreeMap、 TreeSet、 ArrayList のように振舞います。
コレクションインスタンスは、値型として普通に振舞います。永続化オブジェクトに参照されたときに自動的に永続化され、参照がなくなったときに自動的に削除されます。もしある永続化オブジェクトから別の永続化オブジェクトに渡されたら、その要素は現在のテーブルから別のテーブルに移動するかもしれません。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
ertyName"
table="tab
le_name"
schema="sc
hema_name"
lazy="true
|extra|false"
inverse="t
rue|false"
cascade="a
ll|none|save-update|delete|all-delete-orphan|delete-orphan"
sort="unso
rted|natural|comparatorClass"
order-by="
column_name asc|desc"
where="arb
itrary sql where condition"
fetch="joi
n|select|subselect"
batch-size
="N"
access="fi
eld|property|ClassName"
optimistic
-lock="true|false"
mutable="t
rue|false"
node="element-name|."
embed-xml="true|false"
>
<key .... />
<map-key .... />
<element .... />
</map
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
コレクションのインスタンスは、データベース内では、そのコレクションを所有するエンティティの外部キーによって識別されます。この外部キーはコレクションテーブルの コレクションキーカラム (またはカラム) と呼ばれます。コレクションキーカラムは <key> 要素によりマッピングします。
外部キーカラムには null 設定制約があるかもしれません。ほとんどのコレクションに当てはまるでしょう。単方向の一対多関連において、外部キーカラムはデフォルトで null を許す設定になっています。よって、 not-null="true" を指定する必要があるかもしれません。
<key column="productSerialNumber" not-null="true"/>
外部キーの制約が ON DELETE CASCADE を使うかもしれません。
<key column="productSerialNumber" on-delete="cascade"/>
<key> 要素のすべての定義については前の章を参照してください。
コレクションは他の Hibernate の型のほとんど(すべての基本型、カスタム型、コンポーネント、他のエンティティへの参照)を格納することができます。次の点は重要な違いになります。コレクションに格納されたオブジェクトが「値」セマンティクスとして扱われるのか (ライフサイクルはコレクションのオーナーに完全に依存します)、もしくはそれ自身のライフサイクルを持った別のエンティティへの参照であるかのかという違いです。後者は、2つのオブジェクト間の「リンク」をコレクションに保持していると見なしているだけです。
格納される型は コレクション要素型 と呼ばれます。コレクション要素は、 <element> または <composite-element> によりマッピングされ、エンティティへの参照の場合には <one-to-many> または <many-to-many> によりマッピングされます。最初の二つは値として要素をマッピングし、次の二つはエンティティの関連をマッピングするのに使われます。
set と bag を除く全てのコレクションマッピングには、コレクションテーブルの中に インデックス用のカラム が必要です。そのカラムに、配列や List のインデックス、もしくは Map のキーをマッピングします。 Map のインデックスは、 <map-key> によりマッピングされた基本型か、 <map-key-many-to-many> によりマッピングされたエンティティの関連か、あるいは <composite-map-key> によりマッピングされたコンポジット型になります。配列かリストのインデックスは、常に integer 型で、 <list-index> 要素によりマッピングします。マッピングされたカラムにはシーケンシャルな整数を格納します (デフォルトでは0から番号が付けられます)。
<list-index
column
="column_name"
base="
0|1|..."/>
| |
| |
<map-key
column
="column_name"
formul
a="any SQL expression"
type="
type_name"
node="@attribute-name"
length="N"/>
| |
| |
| |
<map-key-many-to-many
column
="column_name"
formul
a="any SQL expression"
class="ClassName"
/>
| |
| |
| |
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
="column_name"
formul
a="any SQL expression"
type="
typename"
length="L"
precision="P"
scale="S"
not-null="true|false"
unique="true|false"
node="element-name"
/>
| |
| |
| |
A many-to-many association is specified using the <many-to-many> element.
<many-to-many
column
="column_name"
formul
a="any SQL expression"
class=
"ClassName"
fetch=
"select|join"
unique
="true|false"
not-fo
und="ignore|exception"
entity
-name="EntityName"
proper
ty-ref="propertyNameFromAssociatedClass"
node="element-name"
embed-xml="true|false"
/>
| |
| |
| |
| |
| |
| |
| |
| |
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 のコレクションのセマンティクスをいくつか失います:
エンティティクラスのインスタンスは、2つ以上のコレクションのインスタンスに属してはいけません。
コレクションに含まれるエンティティクラスのインスタンスは、コレクションインデックスの値として2度以上現れてはいけません。
Product から Part への関連は、 Part テーブルへの外部キーカラムと、場合によってはインデックスカラムが必要です。 <one-to-many> タグは、これが一対多関連であることを表しています。
<one-to-many
class=
"ClassName"
not-fo
und="ignore|exception"
entity
-name="EntityName"
node="element-name"
embed-xml="true|false"
/>
|
|
|
|
|
|
<one-to-many> 要素はカラムを宣言する必要がないことに注意してください。同様に テーブル 名を指定する必要もありません。
If the foreign key column of a <one-to-many> association is declared NOT NULL, you must declare the <key> mapping not-null="true" or use a bidirectional association with the collection mapping marked inverse="true". See the discussion of bidirectional associations later in this chapter for more information.
次の例は、名称(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.SortedMap と java.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 属性に設定できる値は unsorted と natural および、 java.util.Comparator を実装したクラスの名前です。
ソートされたコレクションは実質的には java.util.TreeSet や java.util.TreeMap のように振舞います。
もしデータベース自身にコレクションの要素を並べさせたいなら、 set や bag、map の order-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
>
The value of the order-by attribute is an SQL ordering, not an HQL ordering.
関連は、コレクションの filter() を使うことで、実行時に任意の criteria によってソートすることも可能です。
sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();
双方向関連 は関連のどちら「側」からでもナビゲーションできます。2種類の双方向関連がサポートされています:
片側が set か bag 、もう片方が単一値です。
両側が set か bag です。
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.
3項関連のマッピングには3つのアプローチがあります。1つ目は関連をインデックスとして Map を使用するアプローチです:
<map name="contracts">
<key column="employer_id" not-null="true"/>
<map-key-many-to-many column="employee_id" class="Employee"/>
<one-to-many class="Contract"/>
</map
>
<map name="connections">
<key column="incoming_node_id"/>
<map-key-many-to-many column="outgoing_node_id" class="Node"/>
<many-to-many column="connection_id" class="Connection"/>
</map
>
2つ目は単純に関連をエンティティクラスとしてモデルを作り直すアプローチで、頻繁に使われます。
最後は composite 要素を使うアプローチです。これに関する議論は後ほど行います。
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 childFor 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.
製作著作 © 2004 Red Hat, Inc.