(譯者注:在閱讀本章的時候,以后整個手冊的閱讀過程中,我們都會面臨一個名詞方面的問題,那就是“集合”。"Collections"和"Set"在中文里對應都被翻譯為“集合”,但是他們的含義很不一樣。Collections是一個超集,Set是其中的一種。大部分情況下,本譯稿中泛指的未加英文注明的“集合”,都應當理解為“Collections”。在有些二者同時出現,可能造成混淆的地方,我們用“集合類”來特指“Collecions”,“集合(Set)”來指"Set",一般都會在后面的括號中給出英文。希望大家在閱讀時聯系上下文理解,不要造成誤解。 與此同時,“元素”一詞對應的英文“element”,也有兩個不同的含義。其一為集合的元素,是內存中的一個變量;另一含義則是XML文檔中的一個標簽所代表的元素。也請注意區別。 本章中,特別是后半部分是需要反復閱讀才能理解清楚的。如果遇到任何疑問,請記住,英文版本的reference是惟一標準的參考資料。)
Hibernate要求持久化集合值字段必須聲明為接口,比如:
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 or ArrayList。
集合類實例具有值類型的通常行為。當被持久化對象引用后,他們會自動被持久化,當不再被引用后,自動被刪除。假若實例被從一個持久化對象傳遞到另一個,它的元素可能從一個表轉移到另一個表。兩個實體不能共享同一個集合類實例的引用。因為底層關系數據庫模型的原因,集合值屬性無法支持空值語義;Hibernate對空的集合引用和空集合不加區別。
你不需要過多的為此擔心。就如同你平時使用普通的Java集合類一樣來使用持久化集合類。只是要確認你理解了雙向關聯的語義(后文討論)。
7.2. 集合映射( Collection mappings )
用于映射集合類的Hibernate映射元素取決于接口的類型。比如, <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="propertyName" (1)
table="table_name" (2)
schema="schema_name" (3)
lazy="true|false" (4)
inverse="true|false" (5)
cascade="all|none|save-update|delete|all-delete-orphan" (6)
sort="unsorted|natural|comparatorClass" (7)
order-by="column_name asc|desc" (8)
where="arbitrary sql where condition" (9)
fetch="join|select|subselect" (10)
batch-size="N" (11)
access="field|property|ClassName" (12)
optimistic-lock="true|false" (13)
node="element-name|."
embed-xml="true|false"
>
<key .... />
<map-key .... />
<element .... />
</map>
(1) |
name 集合屬性的名稱
|
(2) |
table (可選——默認為屬性的名稱)這個集合表的名稱(不能在一對多的關聯關系中使用)
|
(3) |
schema (可選) 表的schema的名稱, 他將覆蓋在根元素中定義的schema
|
(4) |
lazy (可選--默認為true) 可以用來關閉延遲加載,指定一直使用預先抓取(對數組不適用)
|
(5) |
inverse (可選——默認為false) 標記這個集合作為雙向關聯關系中的方向一端。
|
(6) |
cascade (可選——默認為none) 讓操作級聯到子實體
|
(7) |
sort(可選)指定集合的排序順序, 其可以為自然的(natural)或者給定一個用來比較的類。
|
(8) |
order-by (可選, 僅用于jdk1.4) 指定表的字段(一個或幾個)再加上asc或者desc(可選), 定義Map,Set和Bag的迭代順序
|
(9) |
where (可選) 指定任意的SQL where條件, 該條件將在重新載入或者刪除這個集合時使用(當集合中的數據僅僅是所有可用數據的一個子集時這個條件非常有用)
|
(10) |
fetch (可選, 默認為select) 用于在外連接抓取、通過后續select抓取和通過后續subselect抓取之間選擇。
|
(11) |
batch-size (可選, 默認為1) 指定通過延遲加載取得集合實例的批處理塊大小("batch size")。
|
(12) |
access(可選-默認為屬性property):Hibernate取得屬性值時使用的策略
|
(12) |
樂觀鎖 (可選 - 默認為 true): 對集合的狀態的改變會是否導致其所屬的實體的版本增長。 (對一對多關聯來說,關閉這個屬性常常是有理的)
|
7.2.1. 集合外鍵(Collection foreign keys)
集合實例在數據庫中依靠持有集合的實體的外鍵加以辨別。此外鍵作為集合關鍵字段(collection key column)(或多個字段)加以引用。集合關鍵字段通過<key> 元素映射。
在外鍵字段上可能具有非空約束。對于大多數集合來說,這是隱含的。對單向一對多關聯來說,外鍵字段默認是可以為空的,因此你可能需要指明 not-null="true"。
<key column="productSerialNumber" not-null="true"/>
外鍵約束可以使用ON DELETE CASCADE。
<key column="productSerialNumber" on-delete="cascade"/>
對<key> 元素的完整定義,請參閱前面的章節。
7.2.2. 集合元素(Collection elements)
集合幾乎可以包含任何其他的Hibernate類型,包括所有的基本類型、自定義類型、組件,當然還有對其他實體的引用。存在一個重要的區別:位于集合中的對象可能是根據“值”語義來操作(其聲明周期完全依賴于集合持有者),或者它可能是指向另一個實體的引用,具有其自己的生命周期。在后者的情況下,被作為集合持有的狀態考慮的,只有兩個對象之間的“連接”。
被包容的類型被稱為集合元素類型(collection element type)。集合元素通過<element>或<composite-element>映射,或在其是實體引用的時候,通過<one-to-many> 或<many-to-many>映射。前兩種用于使用值語義映射元素,后兩種用于映射實體關聯。
7.2.3. 索引集合類(Indexed collections)
所有的集合映射,除了set和bag語義的以外,都需要指定一個集合表的索引字段(index column)——用于對應到數組索引,或者List的索引,或者Map的關鍵字。通過<map-key>,Map 的索引可以是任何基礎類型;若通過<map-key-many-to-many>,它也可以是一個實體引用;若通過<composite-map-key>,它還可以是一個組合類型。數組或列表的索引必須是integer類型,并且使用 <list-index>元素定義映射。被映射的字段包含有順序排列的整數(默認從0開始)。
<map-key
column="column_name" (1)
formula="any SQL expression" (2)
type="type_name" (3)
node="@attribute-name"
length="N"/>
(1) |
column(可選):保存集合索引值的字段名。
|
(2) |
formula (可選): 用于計算map關鍵字的SQL公式
|
(3) |
type (可選,默認為整型integer):集合索引的類型。
|
<map-key-many-to-many
column="column_name" (1)
formula="any SQL expression" (2)(3)
class="ClassName"
/>
(1) |
column(可選):集合索引值中外鍵字段的名稱
|
(2) |
formula (可選): 用于計算map關鍵字的外鍵的SQL公式
|
(3) |
class (必需):集合的索引使用的實體類。
|
假若你的表沒有一個索引字段,當你仍然希望使用List作為屬性類型,你應該把此屬性映射為Hibernate <bag>。從數據庫中獲取的時候,bag不維護其順序,但也可選擇性的進行排序。
從集合類可以產生很大一部分映射,覆蓋了很多常見的關系模型。我們建議你試驗schema生成工具,來體會一下不同的映射聲明是如何被翻譯為數據庫表的。
7.2.4. 值集合于多對多關聯(Collections of values and many-to-many associations)
任何值集合或者多對多關聯需要專用的具有一個或多個外鍵字段的collection table、一個或多個collection element column,以及還可能有一個或多個索引字段。
對于一個值集合, 我們使用<element>標簽。
<element
column="column_name" (1)
formula="any SQL expression" (2)
type="typename" (3)
length="N"
precision="N"
scale="N"
not-null="true|false"
unique="true|false"
node="element-name"
/>
(1) |
column(可選):保存集合元素值的字段名。
|
(2) |
formula (可選): 用于計算元素的SQL公式
|
(3) |
type (必需):集合元素的類型
|
多對多關聯(many-to-many association) 使用 <many-to-many>元素定義.
<many-to-many
column="column_name" (1)
formula="any SQL expression" (2)
class="ClassName" (3)
fetch="select|join" (4)
unique="true|false" (5)
not-found="ignore|exception" (6)
entity-name="EntityName" (7)
node="element-name"
embed-xml="true|false"
/>
(1) |
column(可選): 這個元素的外鍵關鍵字段名
|
(2) |
formula (可選): 用于計算元素外鍵值的SQL公式.
|
(3) |
class (必需): 關聯類的名稱
|
(3) |
outer-join (可選 - 默認為auto): 在Hibernate系統參數中hibernate.use_outer_join被打開的情況下,該參數用來允許使用outer join來載入此集合的數據。
|
(4) |
為此關聯打開外連接抓取或者后續select抓取。這是特殊情況;對于一個實體及其指向其他實體的多對多關聯進全預先抓取(使用一條單獨的SELECT),你不僅需要對集合自身打開join,也需要對<many-to-many>這個內嵌元素打開此屬性。
|
(5) |
對外鍵字段允許DDL生成的時候生成一個惟一約束。這使關聯變成了一個高效的一對多關聯。(此句存疑:原文為This makes the association multiplicity effectively one to many.)
|
(6) |
not-found (可選 - 默認為 exception): 指明引用的外鍵中缺少某些行該如何處理: ignore 會把缺失的行作為一個空引用處理。
|
(7) |
entity-name (可選): 被關聯的類的實體名,作為class的替代。
|
例子:首先, 一組字符串:
<set name="names" table="NAMES">
<key column="GROUPID"/>
<element column="NAME" type="string"/>
</set>
包含一組整數的bag(還設置了order-by參數指定了迭代的順序):
<bag name="sizes"
table="item_sizes"
order-by="size asc">
<key column="item_id"/>
<element column="size" type="integer"/>
</bag>
一個實體數組,在這個案例中是一個多對多的關聯(注意這里的實體是自動管理生命周期的對象(lifecycle objects),cascade="all"):
<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 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>
7.2.5. 一對多關聯(One-to-many Associations)
一對多關聯通過外鍵連接兩個類對應的表,而沒有中間集合表。 這個關系模型失去了一些Java集合的語義:
一個從Product到Part的關聯需要關鍵字字段,可能還有一個索引字段指向Part所對應的表。 <one-to-many>標記指明了一個一對多的關聯。
<one-to-many
class="ClassName" (1)
not-found="ignore|exception" (2)
entity-name="EntityName" (3)
node="element-name"
embed-xml="true|false"
/>
(1) |
class(必須):被關聯類的名稱。
|
(2) |
not-found (可選 - 默認為exception): 指明若緩存的標示值關聯的行缺失,該如何處理: ignore 會把缺失的行作為一個空關聯處理。
|
(3) |
entity-name (可選): 被關聯的類的實體名,作為class的替代。
|
例子
<set name="bars">
<key column="foo_id"/>
<one-to-many class="org.hibernate.Bar"/>
</set>
注意:<one-to-many>元素不需要定義任何字段。 也不需要指定表名。
重要提示:如果一對多關聯中的外鍵字段定義成NOT NULL,你必須把<key>映射聲明為not-null="true",或者使用雙向關聯,并且標明inverse="true"。參閱本章后面關于雙向關聯的討論。
下面的例子展示一個Part實體的map,把name作為關鍵字。( partName 是Part的持久化屬性)。注意其中的基于公式的索引的用法。
<map name="parts"
cascade="all">
<key column="productId" not-null="true"/>
<map-key formula="partName"/>
<one-to-many class="Part"/>
</map>
7.3. 高級集合映射(Advanced collection mappings)
7.3.1. 有序集合(Sorted collections)
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或者更高的jdk版本中才可以實現(通過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>
注意: 這個order-by屬性的值是一個SQL排序子句而不是HQL的!
關聯還可以在運行時使用集合filter()根據任意的條件來排序。
sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();
7.3.2. 雙向關聯(Bidirectional associations)
雙向關聯允許通過關聯的任一端訪問另外一端。在Hibernate中, 支持兩種類型的雙向關聯:
- 一對多(one-to-many)
-
Set或者bag值在一端, 單獨值(非集合)在另外一端
- 多對多(many-to-many)
-
兩端都是set或bag值
要建立一個雙向的多對多關聯,只需要映射兩個many-to-many關聯到同一個數據庫表中,并再定義其中的一端為inverse(使用哪一端要根據你的選擇,但它不能是一個索引集合)。
這里有一個many-to-many的雙向關聯的例子;每一個category都可以有很多items,每一個items可以屬于很多categories:
<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="CATEGORY_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>
如果只對關聯的反向端進行了改變,這個改變不會被持久化。 這表示Hibernate為每個雙向關聯在內存中存在兩次表現,一個從A連接到B,另一個從B連接到A。如果你回想一下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="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="eg.Child">
<id name="id" column="id"/>
....
<many-to-one name="parent"
class="Parent"
column="parent_id"
not-null="true"/>
</class>
在“一”這一端定義inverse="true"不會影響級聯操作,二者是正交的概念!
7.3.3. 三重關聯(Ternary associations)
有三種可能的途徑來映射一個三重關聯。第一種是使用一個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>
第二種方法是簡單的把關聯重新建模為一個實體類。這使我們最經常使用的方法。
最后一種選擇是使用復合元素,我們會在后面討論
如果你完全信奉我們對于“聯合主鍵(composite keys)是個壞東西”,和“實體應該使用(無機的)自己生成的代用標識符(surrogate keys)”的觀點,也許你會感到有一些奇怪,我們目前為止展示的多對多關聯和值集合都是映射成為帶有聯合主鍵的表的!現在,這一點非常值得爭辯;看上去一個單純的關聯表并不能從代用標識符中獲得什么好處(雖然使用組合值的集合可能會獲得一點好處)。不過,Hibernate提供了一個(一點點試驗性質的)功能,讓你把多對多關聯和值集合應得到一個使用代用標識符的表去。
<idbag> 屬性讓你使用bag語義來映射一個List (或Collection)。
<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="eg.Person" outer-join="true"/>
</idbag>
你可以理解,<idbag>人工的id生成器,就好像是實體類一樣!集合的每一行都有一個不同的人造關鍵字。但是,Hibernate沒有提供任何機制來讓你取得某個特定行的人造關鍵字。
注意<idbag>的更新性能要比普通的<bag>高得多!Hibernate可以有效的定位到不同的行,分別進行更新或刪除工作,就如同處理一個list, map或者set一樣。
在目前的實現中,還不支持使用identity標識符生成器策略來生成<idbag>集合的標識符。
7.4. 集合例子(Collection example)
在前面的幾個章節的確非常令人迷惑。 因此讓我們來看一個例子。這個類:
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; }
....
....
}
這個類有一個Child的實例集合。如果每一個子實例至多有一個父實例, 那么最自然的映射是一個one-to-many的關聯關系:
<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
如果父親是必須的, 那么就可以使用雙向one-to-many的關聯了:
<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>
另外一方面,如果一個子實例可能有多個父實例, 那么就應該使用many-to-many關聯:
<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
posted on 2007-04-03 10:32
???MengChuChen 閱讀(700)
評論(0) 編輯 收藏 所屬分類:
hibernate