6.1.?映射定義(Mapping declaration)
對象和關系數據庫之間的映射通常是用一個XML文檔(XML document)來定義的。這個映射文檔被設計為易讀的, 并且可以手工修改。映射語言是以Java為中心,這意味著映射文檔是按照持久化類的定義來創建的, 而非表的定義。
請注意,雖然很多Hibernate用戶選擇手寫XML映射文檔,但也有一些工具可以用來生成映射文檔, 包括XDoclet,Middlegen和AndroMDA。
讓我們從一個映射的例子開始:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="eg">
<class name="Cat"
table="cats"
discriminator-value="C">
<id name="id">
<generator class="native"/>
</id>
<discriminator column="subclass"
type="character"/>
<property name="weight"/>
<property name="birthdate"
type="date"
not-null="true"
update="false"/>
<property name="color"
type="eg.types.ColorUserType"
not-null="true"
update="false"/>
<property name="sex"
not-null="true"
update="false"/>
<property name="litterId"
column="litterId"
update="false"/>
<many-to-one name="mother"
column="mother_id"
update="false"/>
<set name="kittens"
inverse="true"
order-by="litter_id">
<key column="mother_id"/>
<one-to-many class="Cat"/>
</set>
<subclass name="DomesticCat"
discriminator-value="D">
<property name="name"
type="string"/>
</subclass>
</class>
<class name="Dog">
<!-- mapping for Dog could go here -->
</class>
</hibernate-mapping>
我們現在開始討論映射文檔的內容。我們只描述Hibernate在運行時用到的文檔元素和屬性。 映射文檔還包括一些額外的可選屬性和元素,它們在使用schema導出工具的時候會影響導出的數據庫schema結果。 (比如, not-null 屬性。)
所有的XML映射都需要定義如上所示的doctype。DTD可以從上述URL中獲取, 從hibernate-x.x.x/src/net/sf/hibernate目錄中、 或hibernate.jar文件中找到。Hibernate總是會首先在它的classptah中搜索DTD文件。 如果你發現它是通過連接Internet查找DTD文件,就對照你的classpath目錄檢查XML文件里的DTD聲明。
這個元素包括一些可選的屬性。schema和catalog屬性, 指明了這個映射所連接(refer)的表所在的schema和/或catalog名稱。 假若指定了這個屬性,表名會加上所指定的schema和catalog的名字擴展為全限定名。假若沒有指定,表名就不會使用全限定名。 default-cascade指定了未明確注明cascade屬性的Java屬性和 集合類Hibernate會采取什么樣的默認級聯風格。auto-import屬性默認讓我們在查詢語言中可以使用 非全限定名的類名。
<hibernate-mapping
schema="schemaName" (1)
catalog="catalogName" (2)
default-cascade="cascade_style" (3)
default-access="field|property|ClassName" (4)
default-lazy="true|false" (5)
auto-import="true|false" (6)
package="package.name" (7)
/>
(1) |
schema (可選): 數據庫schema的名稱。
|
(2) |
catalog (可選): 數據庫catalog的名稱。
|
(3) |
default-cascade (可選 - 默認為 none): 默認的級聯風格。
|
(4) |
default-access (可選 - 默認為 property): Hibernate用來訪問屬性的策略??梢酝ㄟ^實現PropertyAccessor接口 自定義。
|
(5) |
default-lazy (可選 - 默認為 true): 指定了未明確注明lazy屬性的Java屬性和集合類, Hibernate會采取什么樣的默認加載風格。
|
(6) |
auto-import (可選 - 默認為 true): 指定我們是否可以在查詢語言中使用非全限定的類名(僅限于本映射文件中的類)。
|
(7) |
package (可選): 指定一個包前綴,如果在映射文檔中沒有指定全限定的類名, 就使用這個作為包名。
|
假若你有兩個持久化類,它們的非全限定名是一樣的(就是兩個類的名字一樣,所在的包不一樣--譯者注), 你應該設置auto-import="false"。假若說你把一個“import過”的名字同時對應兩個類, Hibernate會拋出一個異常。
注意hibernate-mapping 元素允許你嵌套多個如上所示的 <class>映射。但是最好的做法(也許一些工具需要的)是一個 持久化類(或一個類的繼承層次)對應一個映射文件,并以持久化的超類名稱命名,例如: Cat.hbm.xml, Dog.hbm.xml,或者如果使用繼承,Animal.hbm.xml。
你可以使用class元素來定義一個持久化類:
<class
name="ClassName" (1)
table="tableName" (2)
discriminator-value="discriminator_value" (3)
mutable="true|false" (4)
schema="owner" (5)
catalog="catalog" (6)
proxy="ProxyInterface" (7)
dynamic-update="true|false" (8)
dynamic-insert="true|false" (9)
select-before-update="true|false" (10)
polymorphism="implicit|explicit" (11)
where="arbitrary sql where condition" (12)
persister="PersisterClass" (13)
batch-size="N" (14)
optimistic-lock="none|version|dirty|all" (15)
lazy="true|false" (16)
entity-name="EntityName" (17)
check="arbitrary sql check condition" (18)
rowid="rowid" (19)
subselect="SQL expression" (20)
abstract="true|false" (21)
entity-name="EntityName" (22)
node="element-name" (23)
/>
(1) |
name (可選): 持久化類(或者接口)的Java全限定名。 如果這個屬性不存在,Hibernate將假定這是一個非POJO的實體映射。
|
(2) |
table (可選 - 默認是類的非全限定名): 對應的數據庫表名。
|
(3) |
discriminator-value (可選 - 默認和類名一樣): 一個用于區分不同的子類的值,在多態行為時使用。它可以接受的值包括 null 和 not null。
|
(4) |
mutable (可選,默認值為true): 表明該類的實例是可變的或者可變的。
|
(5) |
schema (可選): 覆蓋在根<hibernate-mapping>元素中指定的schema名字。
|
(6) |
catalog (可選): 覆蓋在根<hibernate-mapping>元素中指定的catalog名字。
|
(7) |
proxy (可選): 指定一個接口,在延遲裝載時作為代理使用。 你可以在這里使用該類自己的名字。
|
(8) |
dynamic-update (可選, 默認為 false): 指定用于UPDATE 的SQL將會在運行時動態生成,并且只更新那些改變過的字段。
|
(9) |
dynamic-insert (可選, 默認為 false): 指定用于INSERT的 SQL 將會在運行時動態生成,并且只包含那些非空值字段。
|
(10) |
select-before-update (可選, 默認為 false): 指定Hibernate除非確定對象真正被修改了(如果該值為true-譯注),否則不會執行SQL UPDATE操作。在特定場合(實際上,它只在一個瞬時對象(transient object)關聯到一個 新的session中時執行的update()中生效),這說明Hibernate會在UPDATE 之前執行一次額外的SQL SELECT操作,來決定是否應該執行 UPDATE。
|
(11) |
polymorphism(多態) (可選, 默認值為 implicit (隱式) ): 界定是隱式還是顯式的使用多態查詢(這只在Hibernate的具體表繼承策略中用到-譯注)。
|
(12) |
where (可選) 指定一個附加的SQLWHERE 條件, 在抓取這個類的對象時會一直增加這個條件。
|
(13) |
persister (可選): 指定一個定制的ClassPersister。
|
(14) |
batch-size (可選,默認是1) 指定一個用于 根據標識符(identifier)抓取實例時使用的"batch size"(批次抓取數量)。
|
(15) |
optimistic-lock(樂觀鎖定) (可選,默認是version): 決定樂觀鎖定的策略。
|
(16) |
lazy (optional): 通過設置lazy="false", 所有的延遲加載(Lazy fetching)功能將未被激活(disabled)。
|
(17) |
entity-name (可選): Hibernate3允許一個類進行多次映射( 默認情況是映射到不同的表),并且允許使用Maps或XML代替Java層次的實體映射 (也就是實現動態領域模型,不用寫持久化類-譯注)。 更多信息請看第?5.4?節 “動態模型(Dynamic models)” and 第?19?章 XML映射。
|
(18) |
check (可選): 這是一個SQL表達式, 用于為自動生成的schema添加多行(multi-row)約束檢查。
|
(19) |
rowid (可選): Hibernate可以使用數據庫支持的所謂的ROWIDs,例如: Oracle數據庫,如果你設置這個可選的rowid, Hibernate可以使用額外的字段rowid實現快速更新。ROWID是這個功能實現的重點, 它代表了一個存儲元組(tuple)的物理位置。
|
(20) |
subselect (可選): 它將一個不可變(immutable)并且只讀的實體映射到一個數據庫的 子查詢中。它用于實現一個視圖代替一張基本表,但是最好不要這樣做。更多的介紹請看下面內容。
|
(21) |
abstract (可選): 用于在<union-subclass>的繼承結構 (hierarchies)中標識抽象超類。
|
(22) |
entity-name (可選, 默認為類名): 顯式指定實體名
|
若指明的持久化類實際上是一個接口,這也是完全可以接受的。 之后你可以用<subclass>來指定該接口的實際實現類。 你可以持久化任何static(靜態的)內部類。 你應該使用標準的類名格式來指定類名,比如:Foo$Bar。
不可變類,mutable="false"不可以被應用程序更新或者刪除。 這可以讓Hibernate做一些小小的性能優化。
可選的proxy屬性允許延遲加載類的持久化實例。 Hibernate開始會返回實現了這個命名接口的CGLIB代理。當代理的某個方法被實際調用的時候, 真實的持久化對象才會被裝載。參見下面的“用于延遲裝載的代理”。
Implicit
(隱式)的多態是指,如果查詢時給出的是任何超類、該類實現的接口或者該類的 名字,都會返回這個類的實例;如果查詢中給出的是子類的名字,則會返回子類的實例。 Explicit (顯式)的多態是指,只有在查詢時給出明確的該類名字時才會返回這個類的實例; 同時只有在這個<class>的定義中作為<subclass> 或者<joined-subclass>出現的子類,才會可能返回。 在大多數情況下,默認的polymorphism="implicit"都是合適的。 顯式的多態在有兩個不同的類映射到同一個表的時候很有用。(允許一個“輕型”的類,只包含部分表字段)。
persister屬性可以讓你定制這個類使用的持久化策略。 你可以指定你自己實現 org.hibernate.persister.EntityPersister的子類,你甚至可以完全從頭開始編寫一個 org.hibernate.persister.ClassPersister接口的實現, 比如是用儲存過程調用、序列化到文件或者LDAP數據庫來實現。 參閱org.hibernate.test.CustomPersister,這是一個簡單的例子 (“持久化”到一個Hashtable)。
請注意dynamic-update和dynamic-insert的設置并不會繼承到子類, 所以在<subclass>或者<joined-subclass>元素中可能 需要再次設置。這些設置是否能夠提高效率要視情形而定。請用你的智慧決定是否使用。
使用select-before-update通常會降低性能。如果你重新連接一個脫管(detache)對象實例 到一個Session中時,它可以防止數據庫不必要的觸發update。 這就很有用了。
如果你打開了dynamic-update,你可以選擇幾種樂觀鎖定的策略:
我們非常強烈建議你在Hibernate中使用version/timestamp字段來進行樂觀鎖定。 對性能來說,這是最好的選擇,并且這也是唯一能夠處理在session外進行操作的策略(例如: 在使用Session.merge()的時候)。
對Hibernate映射來說視圖和表是沒有區別的,這是因為它們在數據層都是透明的( 注意:一些數據庫不支持視圖屬性,特別是更新的時候)。有時你想使用視圖,但卻不能在數據庫 中創建它(例如:在遺留的schema中)。這樣的話,你可以映射一個不可變的(immutable)并且是 只讀的實體到一個給定的SQL子查詢表達式:
<class name="Summary">
<subselect>
select item.name, max(bid.amount), count(*)
from item
join bid on bid.item_id = item.id
group by item.name
</subselect>
<synchronize table="item"/>
<synchronize table="bid"/>
<id name="name"/>
...
</class>
定義這個實體用到的表為同步(synchronize),確保自動刷新(auto-flush)正確執行, 并且依賴原實體的查詢不會返回過期數據。<subselect>在屬性元素 和一個嵌套映射元素中都可見。
被映射的類必須定義對應數據庫表主鍵字段。大多數類有一個JavaBeans風格的屬性, 為每一個實例包含唯一的標識。<id> 元素定義了該屬性到數據庫表主鍵字段的映射。
<id
name="propertyName" (1)
type="typename" (2)
column="column_name" (3)
unsaved-value="null|any|none|undefined|id_value" (4)
access="field|property|ClassName" (5)
node="element-name|@attribute-name|element/@attribute|.">
<generator class="generatorClass"/>
</id>
(1) |
name (可選): 標識屬性的名字。
|
(2) |
type (可選): 標識Hibernate類型的名字。
|
(3) |
column (可選 - 默認為屬性名): 主鍵字段的名字。
|
(4) |
unsaved-value (可選 - 默認為一個字段判斷(sensible)的值): 一個特定的標識屬性值,用來標志該實例是剛剛創建的,尚未保存。 這可以把這種實例和從以前的session中裝載過(可能又做過修改--譯者注) 但未再次持久化的實例區分開來。
|
(5) |
access (可選 - 默認為property): Hibernate用來訪問屬性值的策略。
|
如果 name屬性不存在,會認為這個類沒有標識屬性。
unsaved-value 屬性很重要!如果你的類的標識屬性不是默認為 正常的Java默認值(null或零),你應該指定正確的默認值。
還有一個另外的<composite-id>定義可以訪問舊式的多主鍵數據。 我們強烈不建議使用這種方式。
可選的<generator>子元素是一個Java類的名字, 用來為該持久化類的實例生成唯一的標識。如果這個生成器實例需要某些配置值或者初始化參數, 用<param>元素來傳遞。
<id name="id" type="long" column="cat_id">
<generator class="org.hibernate.id.TableHiLoGenerator">
<param name="table">uid_table</param>
<param name="column">next_hi_value_column</param>
</generator>
</id>
所有的生成器都實現net.sf.hibernate.id.IdentifierGenerator接口。 這是一個非常簡單的接口;某些應用程序可以選擇提供他們自己特定的實現。當然, Hibernate提供了很多內置的實現。下面是一些內置生成器的快捷名字:
-
increment
-
用于為long, short或者int類型生成 唯一標識。只有在沒有其他進程往同一張表中插入數據時才能使用。 在集群下不要使用。
-
identity
-
對DB2,MySQL, MS SQL Server, Sybase和HypersonicSQL的內置標識字段提供支持。 返回的標識符是long, short 或者int類型的。
-
sequence
-
在DB2,PostgreSQL, Oracle, SAP DB, McKoi中使用序列(sequence), 而在Interbase中使用生成器(generator)。返回的標識符是long, short或者 int類型的。
-
hilo
-
使用一個高/低位算法高效的生成long, short 或者 int類型的標識符。給定一個表和字段(默認分別是是 hibernate_unique_key 和next_hi)作為高位值的來源。 高/低位算法生成的標識符只在一個特定的數據庫中是唯一的。
-
seqhilo
-
使用一個高/低位算法來高效的生成long, short 或者 int類型的標識符,給定一個數據庫序列(sequence)的名字。
-
uuid
-
用一個128-bit的UUID算法生成字符串類型的標識符, 這在一個網絡中是唯一的(使用了IP地址)。UUID被編碼為一個32位16進制數字的字符串。
-
guid
-
在MS SQL Server 和 MySQL 中使用數據庫生成的GUID字符串。
-
native
-
根據底層數據庫的能力選擇identity, sequence 或者hilo中的一個。
-
assigned
-
讓應用程序在save()之前為對象分配一個標示符。這是 <generator>元素沒有指定時的默認生成策略。
-
select
-
通過數據庫觸發器選擇一些唯一主鍵的行并返回主鍵值來分配一個主鍵。
-
foreign
-
使用另外一個相關聯的對象的標識符。通常和<one-to-one>聯合起來使用。
6.1.4.2.?高/低位算法(Hi/Lo Algorithm)
hilo 和 seqhilo生成器給出了兩種hi/lo算法的實現, 這是一種很令人滿意的標識符生成算法。第一種實現需要一個“特殊”的數據庫表來保存下一個可用的“hi”值。 第二種實現使用一個Oracle風格的序列(在被支持的情況下)。
<id name="id" type="long" column="cat_id">
<generator class="hilo">
<param name="table">hi_value</param>
<param name="column">next_value</param>
<param name="max_lo">100</param>
</generator>
</id>
<id name="id" type="long" column="cat_id">
<generator class="seqhilo">
<param name="sequence">hi_value</param>
<param name="max_lo">100</param>
</generator>
</id>
很不幸,你在為Hibernate自行提供Connection時無法使用hilo。 當Hibernate使用JTA獲取應用服務器的數據源連接時,你必須正確地配置 hibernate.transaction.manager_lookup_class。
6.1.4.3.?UUID算法(UUID Algorithm )
UUID包含:IP地址,JVM的啟動時間(精確到1/4秒),系統時間和一個計數器值(在JVM中唯一)。 在Java代碼中不可能獲得MAC地址或者內存地址,所以這已經是我們在不使用JNI的前提下的能做的最好實現了。
6.1.4.4.?標識字段和序列(Identity columns and Sequences)
對于內部支持標識字段的數據庫(DB2,MySQL,Sybase,MS SQL),你可以使用identity關鍵字生成。 對于內部支持序列的數據庫(DB2,Oracle, PostgreSQL, Interbase, McKoi,SAP DB), 你可以使用sequence風格的關鍵字生成。 這兩種方式對于插入一個新的對象都需要兩次SQL查詢。
<id name="id" type="long" column="person_id">
<generator class="sequence">
<param name="sequence">person_id_sequence</param>
</generator>
</id>
<id name="id" type="long" column="person_id" unsaved-value="0">
<generator class="identity"/>
</id>
對于跨平臺開發,native策略會從identity, sequence 和hilo中進行選擇,選擇哪一個,這取決于底層數據庫的支持能力。
6.1.4.5.?程序分配的標識符(Assigned Identifiers)
如果你需要應用程序分配一個標示符(而非Hibernate來生成),你可以使用assigned 生成器。這種特殊的生成器會使用已經分配給對象的標識符屬性的標識符值。 這個生成器使用一個自然鍵(natural key,有商業意義的列-譯注)作為主鍵,而不是使用一個代理鍵( surrogate key,沒有商業意義的列-譯注)。
當選擇assigned生成器時,除非有一個version或timestamp屬性,或者你定義了 Interceptor.isUnsaved(),否則需要讓Hiberante使用 unsaved-value="undefined",強制Hibernatet查詢數據庫來確定一個實例是瞬時的(transient) 還是脫管的(detached)。
6.1.4.6.?觸發器實現的主鍵生成器(Primary keys assigned by triggers)
僅僅用于遺留的schema中 (Hibernate不能使用觸發器生成DDL)。
<id name="id" type="long" column="person_id">
<generator class="select">
<param name="key">socialSecurityNumber</param>
</generator>
</id>
在上面的例子中,類定義了一個命名為socialSecurityNumber的唯一值屬性, 它是一個自然鍵(natural key),命名為person_id的代理鍵(surrogate key) 的值由觸發器生成。
<composite-id
name="propertyName"
class="ClassName"
unsaved-value="undefined|any|none"
access="field|property|ClassName"
node="element-name|."
>
<key-property name="propertyName" type="typename" column="column_name"/>
<key-many-to-one name="propertyName class="ClassName" column="column_name"/>
......
</composite-id>
For a table with a composite key, you may map multiple properties of the class as identifier properties. The <composite-id> element accepts <key-property> property mappings and <key-many-to-one> mappings as child elements.
如果表使用聯合主鍵,你可以映射類的多個屬性為標識符屬性。 <composite-id>元素接受<key-property> 屬性映射和<key-many-to-one>屬性映射作為子元素。
<composite-id>
<key-property name="medicareNumber"/>
<key-property name="dependent"/>
</composite-id>
你的持久化類必須重載equals()和 hashCode()方法,來實現組合的標識符的相等判斷。 實現Serializable接口也是必須的。
不幸的是,這種組合關鍵字的方法意味著一個持久化類是它自己的標識。除了對象自己之外, 沒有什么方便的“把手”可用。你必須自己初始化持久化類的實例,在使用組合關鍵字load() 持久化狀態之前,必須填充他的聯合屬性。我們會在第?9.4?節 “組件作為聯合標識符(Components as composite identifiers)”章中說明一種 更加便捷的方法,把聯合標識實現為一個獨立的類,下面描述的屬性只對這種備用方法有效:
-
name (可選):一個組件類型,持有復合標識(參見下一節)。
-
class (可選 - 默認為通過反射(reflection)得到的屬性類型) : 作為聯合標識的組件類名(參見下一節)。
-
unsaved-value (可選 - 默認為 undefined): 如果設置為any,就表示瞬時(transient)實例應該被重新初始化,或者如果 設置為none,則表示該實例是脫管對象。最好在所有的情況下都保持默認的值。
6.1.6.?鑒別器(discriminator)
在"一棵對象繼承樹對應一個表"的策略中,<discriminator>元素是必需的, 它定義了表的鑒別器字段。鑒別器字段包含標志值,用于告知持久化層應該為某個特定的行創建哪一個子類的實例。 如下這些受到限制的類型可以使用: string, character, integer, byte, short, boolean, yes_no, true_false.
<discriminator
column="discriminator_column" (1)
type="discriminator_type" (2)
force="true|false" (3)
insert="true|false" (4)
formula="arbitrary sql expression" (5)
/>
(1) |
column (可選 - 默認為 class) 鑒別器字段的名字
|
(2) |
type (可選 - 默認為 string) 一個Hibernate字段類型的名字
|
(3) |
force(強制) (可選 - 默認為 false) "強制"Hibernate指定允許的鑒別器值,就算取得的所有實例都是根類的。
|
(4) |
insert (可選 - 默認為true) 如果你的鑒別器字段也是映射為復合標識(composite identifier)的一部分,則需將 這個值設為false。(告訴Hibernate在做SQL INSERT 時不包含該列)
|
(5) |
formula (可選) 一個SQL表達式,在類型判斷(判斷是父類還是具體子類-譯注)時執行??捎糜诨趦热莸蔫b別器。
|
鑒別器字段的實際值是根據<class>和<subclass>元素中 的discriminator-value屬性得來的。
force屬性僅僅是在表包含一些未指定應該映射到哪個持久化類的時候才是有用的。 這種情況不會經常遇到。
使用formula屬性你可以定義一個SQL表達式,用來判斷一個行數據的類型。
<discriminator
formula="case when CLASS_TYPE in ('a', 'b', 'c') then 0 else 1 end"
type="integer"/>
<version>元素是可選的,表明表中包含附帶版本信息的數據。 這在你準備使用 長事務(long transactions)的時候特別有用。(見后)
<version
column="version_column" (1)
name="propertyName" (2)
type="typename" (3)
access="field|property|ClassName" (4)
unsaved-value="null|negative|undefined" (5)
node="element-name|@attribute-name|element/@attribute|."
/>
(1) |
column (可選 - 默認為屬性名): 指定持有版本號的字段名。
|
(2) |
name: 持久化類的屬性名。
|
(3) |
type (可選 - 默認是 integer): 版本號的類型。
|
(4) |
access (可選 - 默認是 property): Hibernate用于訪問屬性值的策略。
|
(5) |
unsaved-value (可選 - 默認是undefined): 用于標明某個實例時剛剛被實例化的(尚未保存)版本屬性值,依靠這個值就可以把這種情況 和已經在先前的session中保存或裝載的脫管(detached)實例區分開來。 (undefined指明使用標識屬性值進行判斷。)
|
版本號必須是以下類型:long, integer, short, timestamp或者calendar。
一個脫管(detached)實例的version或timestamp不能為空(null),因為Hibernate不管 unsaved-value指定為何種策略,它將分離任何屬性為空的version或timestamp 實例為瞬時(transient)實例。 避免Hibernate中的傳遞重附(transitive reattachment)問題的一個簡單方法是 定義一個不能為空的version或timestamp屬性,特別是在人們使用程序分配的標識符(assigned identifiers) 或復合主鍵時非常有用!
6.1.8.?timestamp (optional)
可選的<timestamp>元素指明了表中包含時間戳數據。 這用來作為版本的替代。時間戳本質上是一種對樂觀鎖定的一種不是特別安全的實現。當然, 有時候應用程序可能在其他方面使用時間戳。
<timestamp
column="timestamp_column" (1)
name="propertyName" (2)
access="field|property|ClassName" (3)
unsaved-value="null|undefined" (4)
node="element-name|@attribute-name|element/@attribute|."
/>
(1) |
column (可選 - 默認為屬性名): 持有時間戳的字段名。
|
(2) |
name: 在持久化類中的JavaBeans風格的屬性名, 其Java類型是 Date 或者 Timestamp的。
|
(3) |
access (可選 - 默認是 property): Hibernate用于訪問屬性值的策略。
|
(4) |
unsaved-value (可選 - 默認是null): 用于標明某個實例時剛剛被實例化的(尚未保存)版本屬性值,依靠這個值就可以把這種情況和 已經在先前的session中保存或裝載的脫管(detached)實例區分開來。(undefined 指明使用標識屬性值進行這種判斷。)
|
注意,<timestamp> 和<version type="timestamp">是等價的。
<property>元素為類定義了一個持久化的,JavaBean風格的屬性。
<property
name="propertyName" (1)
column="column_name" (2)
type="typename" (3)
update="true|false" (4)
insert="true|false" (4)
formula="arbitrary SQL expression" (5)
access="field|property|ClassName" (6)
lazy="true|false" (7)
unique="true|false" (8)
not-null="true|false" (9)
optimistic-lock="true|false" (10)
node="element-name|@attribute-name|element/@attribute|."
/>
(1) |
name: 屬性的名字,以小寫字母開頭。
|
(2) |
column (可選 - 默認為屬性名字): 對應的數據庫字段名。 也可以通過嵌套的<column>元素指定。
|
(3) |
type (可選): 一個Hibernate類型的名字。
|
(4) |
update, insert (可選 - 默認為 true) : 表明用于UPDATE 和/或 INSERT 的SQL語句中是否包含這個被映射了的字段。這二者如果都設置為false 則表明這是一個“外源性(derived)”的屬性,它的值來源于映射到同一個(或多個) 字段的某些其他屬性,或者通過一個trigger(觸發器)或其他程序。
|
(5) |
formula (可選): 一個SQL表達式,定義了這個計算 (computed) 屬性的值。計算屬性沒有和它對應的數據庫字段。
|
(6) |
access (可選 - 默認值為 property): Hibernate用來訪問屬性值的策略。
|
(7) |
lazy (可選 - 默認為 false): 指定 指定實例變量第一次被訪問時,這個屬性是否延遲抓?。╢etched lazily)( 需要運行時字節碼增強)。
|
(8) |
unique (可選): 使用DDL為該字段添加唯一的約束。 此外,這也可以用作property-ref的目標屬性。
|
(9) |
not-null (可選): 使用DDL為該字段添加可否為空(nullability)的約束。
|
(10) |
optimistic-lock (可選 - 默認為 true): 指定這個屬性在做更新時是否需要獲得樂觀鎖定(optimistic lock)。 換句話說,它決定這個屬性發生臟數據時版本(version)的值是否增長。
|
typename
可以是如下幾種:
-
Hibernate基礎類型之一(比如:integer, string, character,date, timestamp, float, binary, serializable, object, blob)。
-
一個Java類的名字,這個類屬于一種默認基礎類型 (比如: int, float,char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob)。
-
一個可以序列化的Java類的名字。
-
一個自定義類型的類的名字。(比如: com.illflow.type.MyCustomType)。
如果你沒有指定類型,Hibernarte會使用反射來得到這個名字的屬性,以此來猜測正確的Hibernate類型。 Hibernate會按照規則2,3,4的順序對屬性讀取器(getter方法)的返回類進行解釋。然而,這還不夠。 在某些情況下你仍然需要type屬性。(比如,為了區別Hibernate.DATE 和Hibernate.TIMESTAMP,或者為了指定一個自定義類型。)
access屬性用來讓你控制Hibernate如何在運行時訪問屬性。在默認情況下, Hibernate會使用屬性的get/set方法對(pair)。如果你指明access="field", Hibernate會忽略get/set方法對,直接使用反射來訪問成員變量。你也可以指定你自己的策略, 這就需要你自己實現org.hibernate.property.PropertyAccessor接口, 再在access中設置你自定義策略類的名字。
衍生屬性(derive propertie)是一個特別強大的特征。這些屬性應該定義為只讀,屬性值在裝載時計算生成。 你用一個SQL表達式生成計算的結果,它會在這個實例轉載時翻譯成一個SQL查詢的SELECT 子查詢語句。
<property name="totalPrice"
formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p
WHERE li.productId = p.productId
AND li.customerId = customerId
AND li.orderNumber = orderNumber )"/>
注意,你可以使用實體自己的表,而不用為這個特別的列定義別名( 上面例子中的customerId)。同時注意,如果你不喜歡使用屬性, 你可以使用嵌套的<formula>映射元素。
通過many-to-one元素,可以定義一種常見的與另一個持久化類的關聯。 這種關系模型是多對一關聯(實際上是一個對象引用-譯注):這個表的一個外鍵引用目標表的 主鍵字段。
<many-to-one
name="propertyName" (1)
column="column_name" (2)
class="ClassName" (3)
cascade="cascade_style" (4)
fetch="join|select" (5)
update="true|false" (6)
insert="true|false" (6)
property-ref="propertyNameFromAssociatedClass" (7)
access="field|property|ClassName" (8)
unique="true|false" (9)
not-null="true|false" (10)
optimistic-lock="true|false" (11)
lazy="true|proxy|false" (12)
not-found="ignore|exception" (13)
entity-name="EntityName" (14)
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
/>
(1) |
name: 屬性名。
|
(2) |
column (可選): 外間字段名。它也可以通過嵌套的 <column>元素指定。
|
(3) |
class (可選 - 默認是通過反射得到屬性類型): 關聯的類的名字。
|
(4) |
cascade(級聯) (可選): 指明哪些操作會從父對象級聯到關聯的對象。
|
(5) |
fetch (可選 - 默認為 select): 在外連接抓取(outer-join fetching)和序列選擇抓取(sequential select fetching)兩者中選擇其一。
|
(6) |
update, insert (可選 - defaults to true) 指定對應的字段是否包含在用于UPDATE 和/或 INSERT 的SQL語句中。如果二者都是false,則這是一個純粹的 “外源性(derived)”關聯,它的值是通過映射到同一個(或多個)字段的某些其他屬性得到 或者通過trigger(觸發器)、或其他程序。
|
(7) |
property-ref: (可選) 指定關聯類的一個屬性,這個屬性將會和本外鍵相對應。 如果沒有指定,會使用對方關聯類的主鍵。
|
(8) |
access (可選 - 默認是 property): Hibernate用來訪問屬性的策略。
|
(9) |
unique (可選): 使用DDL為外鍵字段生成一個唯一約束。此外, 這也可以用作property-ref的目標屬性。這使關聯同時具有 一對一的效果。
|
(10) |
not-null (可選): 使用DDL為外鍵字段生成一個非空約束。
|
(11) |
optimistic-lock (可選 - 默認為 true): 指定這個屬性在做更新時是否需要獲得樂觀鎖定(optimistic lock)。 換句話說,它決定這個屬性發生臟數據時版本(version)的值是否增長。
|
(12) |
lazy (可選 - 默認為 proxy): 默認情況下,單點關聯是經過代理的。lazy="true"指定此屬性應該在實例變量第一次被訪問時應該延遲抓?。╢etche lazily)(需要運行時字節碼的增強)。 lazy="false"指定此關聯總是被預先抓取。
|
(13) |
not-found (可選 - 默認為 exception): 指定外鍵引用的數據不存在時如何處理: ignore會將數據不存在作為關聯到一個空對象(null)處理。
|
(14) |
entity-name (optional): 被關聯的類的實體名。
|
cascade屬性設置為除了none以外任何有意義的值, 它將把特定的操作傳播到關聯對象中。這個值就代表著Hibernate基本操作的名稱, persist, merge, delete, save-update, evict, replicate, lock, refresh, 以及特別的值delete-orphan和all,并且可以用逗號分隔符 來合并這些操作,例如,cascade="persist,merge,evict"或 cascade="all,delete-orphan"。更全面的解釋請參考第?11.11?節 “傳播性持久化(transitive persistence)”.
一個典型的簡單many-to-one定義例子:
<many-to-one name="product" class="Product" column="PRODUCT_ID"/>
property-ref屬性只應該用來對付老舊的數據庫系統, 可能有外鍵指向對方關聯表的是個非主鍵字段(但是應該是一個惟一關鍵字)的情況下。 這是一種十分丑陋的關系模型。比如說,假設Product類有一個惟一的序列號, 它并不是主鍵。(unique屬性控制Hibernate通過SchemaExport工具生成DDL的過程。)
<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>
那么關于OrderItem 的映射可能是:
<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>
當然,我們決不鼓勵這種用法。
如果被引用的唯一主鍵由關聯實體的多個屬性組成,你應該在名稱為<properties>的元素 里面映射所有關聯的屬性。
持久化對象之間一對一的關聯關系是通過one-to-one元素定義的。
<one-to-one
name="propertyName" (1)
class="ClassName" (2)
cascade="cascade_style" (3)
constrained="true|false" (4)
fetch="join|select" (5)
property-ref="propertyNameFromAssociatedClass" (6)
access="field|property|ClassName" (7)
formula="any SQL expression" (8)
lazy="true|proxy|false" (9)
entity-name="EntityName" (10)
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
/>
(1) |
name: 屬性的名字。
|
(2) |
class (可選 - 默認是通過反射得到的屬性類型):被關聯的類的名字。
|
(3) |
cascade(級聯) (可選) 表明操作是否從父對象級聯到被關聯的對象。
|
(4) |
constrained(約束) (可選) 表明該類對應的表對應的數據庫表,和被關聯的對象所對應的數據庫表之間,通過一個外鍵引用對主鍵進行約束。 這個選項影響save()和delete()在級聯執行時的先后順序以及 決定該關聯能否被委托(也在schema export tool中被使用).
|
(5) |
fetch (可選 - 默認設置為選擇): 在外連接抓取或者序列選擇抓取選擇其一.
|
(6) |
property-ref: (可選) 指定關聯類的屬性名,這個屬性將會和本類的主鍵相對應。如果沒有指定,會使用對方關聯類的主鍵。
|
(7) |
access (可選 - 默認是 property): Hibernate用來訪問屬性的策略。
|
(8) |
formula (可選):絕大多數一對一的關聯都指向其實體的主鍵。在一些少見的情況中, 你可能會指向其他的一個或多個字段,或者是一個表達式,這些情況下,你可以用一個SQL公式來表示。 (可以在org.hibernate.test.onetooneformula找到例子)
|
(9) |
lazy (可選 - 默認為 proxy): 默認情況下,單點關聯是經過代理的。lazy="true"指定此屬性應該在實例變量第一次被訪問時應該延遲抓?。╢etche lazily)(需要運行時字節碼的增強)。 lazy="false"指定此關聯總是被預先抓取。注意,如果constrained="false", 不可能使用代理,Hibernate會采取預先抓??!
|
(10) |
entity-name (可選): 被關聯的類的實體名。
|
有兩種不同的一對一關聯:
主鍵關聯不需要額外的表字段;如果兩行是通過這種一對一關系相關聯的,那么這兩行就共享同樣的主關鍵字值。所以如果你希望兩個對象通過主鍵一對一關聯,你必須確認它們被賦予同樣的標識值!
比如說,對下面的Employee和Person進行主鍵一對一關聯:
<one-to-one name="person" class="Person"/>
<one-to-one name="employee" class="Employee" constrained="true"/>
現在我們必須確保PERSON和EMPLOYEE中相關的字段是相等的。我們使用一個被成為foreign的特殊的hibernate標識符生成策略:
<class name="person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="foreign">
<param name="property">employee</param>
</generator>
</id>
...
<one-to-one name="employee"
class="Employee"
constrained="true"/>
</class>
一個剛剛保存的Person實例被賦予和該Person的employee屬性所指向的Employee實例同樣的關鍵字值。
另一種方式是一個外鍵和一個惟一關鍵字對應,上面的Employee和Person的例子,如果使用這種關聯方式,可以表達成:
<many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/>
如果在Person的映射加入下面幾句,這種關聯就是雙向的:
<one-to-one name"employee" class="Employee" property-ref="person"/>
6.1.12.?組件(component), 動態組件(dynamic-component)
<component>元素把子對象的一些元素與父類對應的表的一些字段映射起來。 然后組件可以定義它們自己的屬性、組件或者集合。參見后面的“Components”一章。
<component
name="propertyName" (1)
class="className" (2)
insert="true|false" (3)
update="true|false" (4)
access="field|property|ClassName" (5)
lazy="true|false" (6)
optimistic-lock="true|false" (7)
unique="true|false" (8)
node="element-name|."
>
<property ...../>
<many-to-one .... />
........
</component>
(1) |
name: 屬性名
|
(2) |
class (可選 - 默認為通過反射得到的屬性類型):組件(子)類的名字。
|
(3) |
insert: 被映射的字段是否出現在SQL的INSERT語句中?
|
(4) |
update: 被映射的字段是否出現在SQL的UPDATE語句中?
|
(5) |
access (可選 - 默認是 property): Hibernate用來訪問屬性的策略。
|
(6) |
lazy (可選 - 默認是 false): 表明此組件應在實例變量第一次被訪問的時候延遲加載(需要編譯時字節碼裝置器)
|
(7) |
optimistic-lock (可選 - 默認是 true):表明更新此組件是否需要獲取樂觀鎖。換句話說,當這個屬性變臟時,是否增加版本號(Version)
|
(8) |
unique (可選 - 默認是 false):表明組件映射的所有字段上都有唯一性約束
|
其<property>子標簽為子類的一些屬性與表字段之間建立映射。
<component>元素允許加入一個<parent>子元素,在組件類內部就可以有一個指向其容器的實體的反向引用。
<dynamic-component>元素允許把一個Map映射為組件,其屬性名對應map的鍵值。 參見第?9.5?節 “動態組件 (Dynamic components)”.
<properties> 元素允許定義一個命名的邏輯分組(grouping)包含一個類中的多個屬性。 這個元素最重要的用處是允許多個屬性的組合作為property-ref的目標(target)。 這也是定義多字段唯一約束的一種方便途徑。
<properties
name="logicalName" (1)
insert="true|false" (2)
update="true|false" (3)
optimistic-lock="true|false" (4)
unique="true|false" (5)
>
<property ...../>
<many-to-one .... />
........
</properties>
(1) |
name: 分組的邏輯名稱 - 不是 實際屬性的名稱.
|
(2) |
insert: 被映射的字段是否出現在SQL的 INSERT語句中?
|
(3) |
update: 被映射的字段是否出現在SQL的 UPDATE語句中?
|
(4) |
optimistic-lock (可選 - 默認是 true):表明更新此組件是否需要獲取樂觀鎖。換句話說,當這個屬性變臟時,是否增加版本號(Version)
|
(5) |
unique (可選 - 默認是 false):表明組件映射的所有字段上都有唯一性約束
|
例如,如果我們有如下的<properties>映射:
<class name="Person">
<id name="personNumber"/>
...
<properties name="name"
unique="true" update="false">
<property name="firstName"/>
<property name="initial"/>
<property name="lastName"/>
</properties>
</class>
然后,我們可能有一些遺留的數據關聯,引用 Person表的這個唯一鍵,而不是主鍵。
<many-to-one name="person"
class="Person" property-ref="name">
<column name="firstName"/>
<column name="initial"/>
<column name="lastName"/>
</many-to-one>
我們并不推薦這樣使用,除非在映射遺留數據的情況下。
最后,多態持久化需要為父類的每個子類都進行定義。對于“每一棵類繼承樹對應一個表”的策略來說,就需要使用<subclass>定義。
<subclass
name="ClassName" (1)
discriminator-value="discriminator_value" (2)
proxy="ProxyInterface" (3)
lazy="true|false" (4)
dynamic-update="true|false"
dynamic-insert="true|false"
entity-name="EntityName"
node="element-name">
<property .... />
.....
</subclass>
(1) |
name: 子類的全限定名。
|
(2) |
discriminator-value(辨別標志) (可選 - 默認為類名):一個用于區分每個獨立的子類的值。
|
(3) |
proxy(代理) (可選): 指定一個類或者接口,在延遲裝載時作為代理使用。
|
(4) |
lazy (可選, 默認是true): 設置為 lazy="false" 禁止使用延遲抓取
|
每個子類都應該定義它自己的持久化屬性和子類。 <version> 和<id> 屬性可以從根父類繼承下來。在一棵繼承樹上的每個子類都必須定義一個唯一的discriminator-value。如果沒有指定,就會使用Java類的全限定名。
可以在單獨的映射文件中,直接在hibernate-mapping下定義subclass,union-subclass和joined-subclass映射。這樣你只要增加一個新的映射文件就可以繼承一棵類繼承樹。你必須在子類的映射中指定extends 屬性來指定已映射的超類。注意:以前,這個特性使得映射文件的順序變得很重要。從Hibernate3開始,當使用extends關鍵字的時候,映射文件的次序便不重要了。而在單一映射文件中,依舊需要保持將超類定義在子類之前這樣的次序。
<hibernate-mapping>
<subclass name="DomesticCat" extends="Cat" discriminator-value="D">
<property name="name" type="string"/>
</subclass>
</hibernate-mapping>
更多關于繼承映射的信息, 參考 第?10?章 繼承映射(Inheritance Mappings)章節.
6.1.15.?連接的子類(joined-subclass)
此外,每個子類可能被映射到他自己的表中(每個子類一個表的策略)。被繼承的狀態通過和超類的表關聯得到。我們使用<joined-subclass>元素。
<joined-subclass
name="ClassName" (1)
table="tablename" (2)
proxy="ProxyInterface" (3)
lazy="true|false" (4)
dynamic-update="true|false"
dynamic-insert="true|false"
schema="schema"
catalog="catalog"
extends="SuperclassName"
persister="ClassName"
subselect="SQL expression"
entity-name="EntityName"
node="element-name">
<key .... >
<property .... />
.....
</joined-subclass>
(1) |
name: 子類的全限定名。
|
(2) |
table: 子類的表名.
|
(3) |
proxy (可選): 指定一個類或者接口,在延遲裝載時作為代理使用。
|
(4) |
lazy (可選, 默認是 true): 設置為 lazy="false" 禁止使用延遲裝載。
|
這種映射策略不需要指定辨別標志(discriminator)字段。但是,每一個子類都必須使用<key>元素指定一個表字段來持有對象的標識符。本章開始的映射可以被用如下方式重寫:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="eg">
<class name="Cat" table="CATS">
<id name="id" column="uid" type="long">
<generator class="hilo"/>
</id>
<property name="birthdate" type="date"/>
<property name="color" not-null="true"/>
<property name="sex" not-null="true"/>
<property name="weight"/>
<many-to-one name="mate"/>
<set name="kittens">
<key column="MOTHER"/>
<one-to-many class="Cat"/>
</set>
<joined-subclass name="DomesticCat" table="DOMESTIC_CATS">
<key column="CAT"/>
<property name="name" type="string"/>
</joined-subclass>
</class>
<class name="eg.Dog">
<!-- mapping for Dog could go here -->
</class>
</hibernate-mapping>
更多關于繼承映射的信息,參考第?10?章 繼承映射(Inheritance Mappings)。
6.1.16.?聯合子類(union-subclass)
第三種選擇是僅僅映射類繼承樹中具體類部分到表中(每個具體類一張表的策略)。其中,每張表定義了類的所有持久化狀態,包括繼承的狀態。在 Hibernate 中,并不需要完全顯式地映射這樣的繼承樹。你可以簡單地使用單獨的<class>定義映射每個類。然而,如果你想使用多態關聯(例如,一個對類繼承樹中超類的關聯),你需要使用<union-subclass>映射。
<union-subclass
name="ClassName" (1)
table="tablename" (2)
proxy="ProxyInterface" (3)
lazy="true|false" (4)
dynamic-update="true|false"
dynamic-insert="true|false"
schema="schema"
catalog="catalog"
extends="SuperclassName"
abstract="true|false"
persister="ClassName"
subselect="SQL expression"
entity-name="EntityName"
node="element-name">
<property .... />
.....
</union-subclass>
(1) |
name: 子類的全限定名。
|
(2) |
table: 子類的表名
|
(3) |
proxy (可選): 指定一個類或者接口,在延遲裝載時作為代理使用。
|
(4) |
lazy (可選, 默認是 true): 設置為 lazy="false" 禁止使用延遲裝載。
|
這種映射策略不需要指定辨別標志(discriminator)字段。
更多關于繼承映射的信息,參考第?10?章 繼承映射(Inheritance Mappings)。
使用 <join> 元素,可以將一個類的屬性映射到多張表中。
<join
table="tablename" (1)
schema="owner" (2)
catalog="catalog" (3)
fetch="join|select" (4)
inverse="true|false" (5)
optional="true|false"> (6)
<key ... />
<property ... />
...
</join>
(1) |
table: 被連接表的名稱。
|
(2) |
schema (可選):覆蓋由根<hibernate-mapping>元素指定的模式名稱。
|
(3) |
catalog (可選): 覆蓋由根 <hibernate-mapping>元素指定的目錄名稱。
|
(4) |
fetch (可選 - 默認是 join): 如果設置為默認值join, Hibernate 將使用一個內連接來得到這個類或其超類定義的<join>,而使用一個外連接來得到其子類定義的<join>。如果設置為select,則 Hibernate 將為子類定義的 <join>使用順序選擇。這僅在一行數據表示一個子類的對象的時候才會發生。對這個類和其超類定義的<join>,依然會使用內連接得到。
|
(5) |
inverse (可選 - 默認是 false): 如果打開,Hibernate 不會插入或者更新此連接定義的屬性。
|
(6) |
optional (可選 - 默認是 false): 如果打開,Hibernate 只會在此連接定義的屬性非空時插入一行數據,并且總是使用一個外連接來得到這些屬性。
|
例如,一個人(person)的地址(address)信息可以被映射到單獨的表中(并保留所有屬性的值類型語義):
<class name="Person"
table="PERSON">
<id name="id" column="PERSON_ID">...</id>
<join table="ADDRESS">
<key column="ADDRESS_ID"/>
<property name="address"/>
<property name="zip"/>
<property name="country"/>
</join>
...
此特性常常對遺留數據模型有用,我們推薦表個數比類個數少,以及細粒度的領域模型。然而,在單獨的繼承樹上切換繼承映射策略是有用的,后面會解釋這點。
我們目前已經見到過<key>元素多次了。 這個元素在父映射元素定義了對新表的連接,并且在被連接表中定義了一個外鍵引用原表的主鍵的情況下經常使用。
<key
column="columnname" (1)
on-delete="noaction|cascade" (2)
property-ref="propertyName" (3)
not-null="true|false" (4)
update="true|false" (5)
unique="true|false" (6)
/>
(1) |
column (可選): 外鍵字段的名稱。也可以通過嵌套的 <column>指定。
|
(2) |
on-delete (可選, 默認是 noaction): 表明外鍵關聯是否打開數據庫級別的級聯刪除。
|
(3) |
property-ref (可選): 表明外鍵引用的字段不是原表的主鍵(提供給遺留數據)。
|
(4) |
not-null (可選): 表明外鍵的字段不可為空(這意味著無論何時外鍵都是主鍵的一部分)。
|
(5) |
update (可選): 表明外鍵決不應該被更新(這意味著無論何時外鍵都是主鍵的一部分)。
|
(6) |
unique (可選): 表明外鍵應有唯一性約束 (這意味著無論何時外鍵都是主鍵的一部分)。
|
對那些看重刪除性能的系統,我們推薦所有的鍵都應該定義為on-delete="cascade",這樣 Hibernate 將使用數據庫級的ON CASCADE DELETE約束,而不是多個DELETE語句。 注意,這個特性會繞過 Hibernate 通常對版本數據(versioned data)采用的樂觀鎖策略。
not-null 和 update 屬性在映射單向一對多關聯的時候有用。如果你映射一個單向一對多關聯到非空的(non-nullable)外鍵,你必須 用<key not-null="true">定義此鍵字段。
6.1.19.?字段和規則元素(column and formula elements)
任何接受column屬性的映射元素都可以選擇接受<column> 子元素。同樣的,formula也可以替換<formula>屬性。
<column
name="column_name"
length="N"
precision="N"
scale="N"
not-null="true|false"
unique="true|false"
unique-key="multicolumn_unique_key_name"
index="index_name"
sql-type="sql_type_name"
check="SQL expression"/>
<formula>SQL expression</formula>
column 和 formula 屬性甚至可以在同一個屬性或關聯映射中被合并來表達,例如,一些奇異的連接條件。
<many-to-one name="homeAddress" class="Address"
insert="false" update="false">
<column name="person_id" not-null="true" length="10"/>
<formula>'MAILING'</formula>
</many-to-one>
假設你的應用程序有兩個同樣名字的持久化類,但是你不想在Hibernate查詢中使用他們的全限定名。除了依賴auto-import="true"以外,類也可以被顯式地“import(引用)”。你甚至可以引用沒有明確被映射的類和接口。
<import class="java.lang.Object" rename="Universe"/>
<import
class="ClassName" (1)
rename="ShortName" (2)
/>
(1) |
class: 任何Java類的全限定名。
|
(2) |
rename (可選 - 默認為類的全限定名): 在查詢語句中可以使用的名字。
|
這是屬性映射的又一種類型。<any> 映射元素定義了一種從多個表到類的多態關聯。這種類型的映射常常需要多于一個字段。第一個字段持有被關聯實體的類型,其他的字段持有標識符。對這種類型的關聯來說,不可能指定一個外鍵約束,所以這當然不是映射(多態)關聯的通常的方式。你只應該在非常特殊的情況下使用它(比如,審計log,用戶會話數據等等)。
meta-type 屬性使得應用程序能指定一個將數據庫字段的值映射到持久化類的自定義類型。這個持久化類包含有用id-type指定的標識符屬性。 你必須指定從meta-type的值到類名的映射。
<any name="being" id-type="long" meta-type="string">
<meta-value value="TBL_ANIMAL" class="Animal"/>
<meta-value value="TBL_HUMAN" class="Human"/>
<meta-value value="TBL_ALIEN" class="Alien"/>
<column name="table_name"/>
<column name="id"/>
</any>
<any
name="propertyName" (1)
id-type="idtypename" (2)
meta-type="metatypename" (3)
cascade="cascade_style" (4)
access="field|property|ClassName" (5)
optimistic-lock="true|false" (6)
>
<meta-value ... />
<meta-value ... />
.....
<column .... />
<column .... />
.....
</any>
(1) |
name: 屬性名
|
(2) |
id-type: 標識符類型
|
(3) |
meta-type (可選 -默認是 string): 允許辨別標志(discriminator)映射的任何類型
|
(4) |
cascade (可選 -默認是none): 級聯的類型
|
(5) |
access (可選 -默認是 property): Hibernate 用來訪問屬性值的策略。
|
(6) |
optimistic-lock (可選 -默認是 true): 表明更新此組件是否需要獲取樂觀鎖。換句話說,當這個屬性變臟時,是否增加版本號(Version)
|
6.2.1.?實體(Entities)和值(values)
為了理解很多與持久化服務相關的Java語言級對象的行為,我們需要把它們分為兩類:
實體entity
獨立于任何持有實體引用的對象。與通常的Java模型相比,不再被引用的對象會被當作垃圾收集掉。實體必須被顯式的保存和刪除(除非保存和刪除是從父實體向子實體引發的級聯)。這和ODMG模型中關于對象通過可觸及保持持久性有一些不同——比較起來更加接近應用程序對象通常在一個大系統中的使用方法。實體支持循環引用和交叉引用,它們也可以加上版本信息。
一個實體的持久狀態包含指向其他實體和值類型實例的引用。值可以是原始類型,集合(不是集合中的對象),組件或者特定的不可變對象。與實體不同,值(特別是集合和組件)是通過可觸及性來進行持久化和刪除的。因為值對象(和原始類型數據)是隨著包含他們的實體而被持久化和刪除的,他們不能被獨立的加上版本信息。值沒有獨立的標識,所以他們不能被兩個實體或者集合共享。
直到現在,我們都一直使用術語“持久類”(persistent class)來代表實體。我們仍然會這么做。 然而嚴格說來,不是所有的用戶自定義的,帶有持久化狀態的類都是實體。組件就是用戶自定義類,卻是值語義的。java.lang.String類型的java屬性也是值語義的。給了這個定義以后,我們可以說所有JDK提供的類型(類)都是值類型的語義,而用于自定義類型可能被映射為實體類型或值類型語義。采用哪種類型的語義取決于開發人員。在領域模型中,尋找實體類的一個好線索是共享引用指向這個類的單一實例,而組合或聚合通常被轉化為值類型。
我們會在本文檔中重復碰到這兩個概念。
挑戰在于將java類型系統(和開發者定義的實體和值類型)映射到 SQL/數據庫類型系統。Hibernate提供了連接兩個系統之間的橋梁:對于實體類型,我們使用<class>, <subclass> 等等。對于值類型,我們使用 <property>, <component> 及其他,通常跟隨著type屬性。這個屬性的值是Hibernate 的映射類型的名字。Hibernate提供了許多現成的映射(標準的JDK值類型)。你也可以編寫自己的映射類型并實現自定義的變換策略,隨后我們會看到這點。
所有的Hibernate內建類型,除了collections以外,都支持空(null)語義。
The built-in basic mapping types may be roughly categorized into 內建的 基本映射類型可以大致分為
-
integer, long, short, float, double, character, byte, boolean, yes_no, true_false
-
這些類型都對應Java的原始類型或者其封裝類,來符合(特定廠商的)SQL 字段類型。boolean, yes_no 和 true_false都是Java 中boolean 或者java.lang.Boolean的另外說法。
-
string
-
從java.lang.String 到 VARCHAR (或者 Oracle的 VARCHAR2)的映射。
-
date, time, timestamp
-
從java.util.Date和其子類到SQL類型DATE, TIME 和TIMESTAMP (或等價類型)的映射。
-
calendar, calendar_date
-
從java.util.Calendar 到SQL 類型TIMESTAMP和 DATE(或等價類型)的映射。
-
big_decimal, big_integer
-
從java.math.BigDecimal和java.math.BigInteger到NUMERIC (或者 Oracle 的NUMBER類型)的映射。
-
locale, timezone, currency
-
從java.util.Locale, java.util.TimeZone 和java.util.Currency 到VARCHAR (或者 Oracle 的VARCHAR2類型)的映射. Locale和 Currency 的實例被映射為它們的ISO代碼。TimeZone的實例被影射為它的ID。
-
class
-
從java.lang.Class 到 VARCHAR (或者 Oracle 的VARCHAR2類型)的映射。Class被映射為它的全限定名。
-
binary
-
把字節數組(byte arrays)映射為對應的 SQL二進制類型。
-
text
-
把長Java字符串映射為SQL的CLOB或者TEXT類型。
-
serializable
-
把可序列化的Java類型映射到對應的SQL二進制類型。你也可以為一個并非默認為基本類型的可序列化Java類或者接口指定Hibernate類型serializable。
-
clob, blob
-
JDBC 類 java.sql.Clob 和 java.sql.Blob的映射。某些程序可能不適合使用這個類型,因為blob和clob對象可能在一個事務之外是無法重用的。(而且, 驅動程序對這種類型的支持充滿著補丁和前后矛盾。)
實體及其集合的唯一標識可以是除了binary、 blob 和 clob之外的任何基礎類型。(聯合標識也是允許的,后面會說到。)
在org.hibernate.Hibernate中,定義了基礎類型對應的Type常量。比如,Hibernate.STRING代表string 類型。
開發者創建屬于他們自己的值類型也是很容易的。比如說,你可能希望持久化java.lang.BigInteger類型的屬性,持久化成為VARCHAR字段。Hibernate沒有內置這樣一種類型。自定義類型能夠映射一個屬性(或集合元素)到不止一個數據庫表字段。比如說,你可能有這樣的Java屬性:getName()/setName(),這是java.lang.String類型的,對應的持久化到三個字段:FIRST_NAME, INITIAL, SURNAME。
要實現一個自定義類型,可以實現org.hibernate.UserType或org.hibernate.CompositeUserType中的任一個,并且使用類型的Java全限定類名來定義屬性。請查看org.hibernate.test.DoubleStringType這個例子,看看它是怎么做的。
<property name="twoStrings" type="org.hibernate.test.DoubleStringType">
<column name="first_string"/>
<column name="second_string"/>
</property>
注意使用<column>標簽來把一個屬性映射到多個字段的做法。
CompositeUserType, EnhancedUserType, UserCollectionType, 和 UserVersionType 接口為更特殊的使用方式提供支持。
你甚至可以在一個映射文件中提供參數給一個UserType。 為了這樣做,你的UserType必須實現org.hibernate.usertype.ParameterizedType接口。為了給自定義類型提供參數,你可以在映射文件中使用<type>元素。
<property name="priority">
<type name="com.mycompany.usertypes.DefaultValueIntegerType">
<param name="default">0</param>
</type>
</property>
現在,UserType 可以從傳入的Properties對象中得到default 參數的值。
如果你非常頻繁地使用某一UserType,可以為他定義一個簡稱。這可以通過使用 <typedef>元素來實現。Typedefs為一自定義類型賦予一個名稱,并且如果此類型是參數化的,還可以包含一系列默認的參數值。
<typedef class="com.mycompany.usertypes.DefaultValueIntegerType" name="default_zero">
<param name="default">0</param>
</typedef>
<property name="priority" type="default_zero"/>
也可以根據具體案例通過屬性映射中的類型參數覆蓋在typedef中提供的參數。
盡管 Hibernate 內建的豐富的類型和對組件的支持意味著你可能很少 需要使用自定義類型。不過,為那些在你的應用中經常出現的(非實體)類使用自定義類型也是一個好方法。例如,一個MonetaryAmount類使用CompositeUserType來映射是不錯的選擇,雖然他可以很容易地被映射成組件。這樣做的動機之一是抽象。使用自定義類型,以后假若你改變表示金額的方法時,它可以保證映射文件不需要修改。
7.1.?持久化集合類(Persistent collections)
(譯者注:在閱讀本章的時候,以后整個手冊的閱讀過程中,我們都會面臨一個名詞方面的問題,那就是“集合”。"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
更多的例子,以及一個完整的父/子關系映射的排練,請參閱第?22?章 示例:父子關系(Parent Child Relationships).
甚至可能出現更加復雜的關聯映射,我們會在下一章中列出所有可能性。
Component
這個概念在Hibernate中幾處不同的地方為了不同的目的被重復使用.
9.1.?依賴對象(Dependent objects)
Component是一個被包含的對象,它作為值類型被持久化,而非一個被引用的實體。“component(組件)”這一術語指的是面向對象的合成概念(而并不是系統構架層次上的組件的概念)舉個例子, 你可以對人(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;
}
}
現在,姓名(Name)是作為人(Person)的一個組成部分。需要注意的是:需要對姓名 的持久化屬性定義getter和setter方法,但是不需要實現任何的接口或申明標識符字段。
以下是這個例子的Hibernate映射文件:
<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string">
<generator class="uuid.hex"/>
</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)表中將包括pid, birthday, initial, first和 last等字段。
就像所有的值類型一樣, Component不支持共享引用。 換句話說,兩個人可能重名,但是兩個person對象應該包含兩個獨立的name對象,只不過是具有“同樣”的值。 Component的值為空從語義學上來講是專有的(ad hoc)。 每當 重新加載一個包含組件的對象,如果component的所有字段為空,那么將Hibernate將假定整個component為 空。對于絕大多數目的,這樣假定是沒有問題的。
Component的屬性可以是Hibernate類型(包括Collections, many-to-one 關聯, 以及其它Component 等等)。嵌套Component不應該作為特殊的應用被考慮(Nested components should not be considered an exotic usage)。 Hibernate趨向于支持設計細致(fine-grained)的對象模型。
<component> 元素還允許有 <parent>子元素 ,用來表明component類中的一個屬性返回包含它的實體的引用。
<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string">
<generator class="uuid.hex"/>
</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支持component的集合(例如: 一個元素是“姓名”這種類型的數組)。 你可以使用<composite-element>標簽替代<element>標簽來定義你的component集合。
<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()是非常重要的。
組合元素可以包含component但是不能包含集合。如果你的組合元素自身包含component, 必須使用<nested-composite-element>標簽。這是一個相當特殊的案例 - 組合元素的集合自身可以包含component。 這個時候你就應該考慮一下使用one-to-many關聯是否會更恰當。 嘗試對這個組合元素重新建模為一個實體-但是需要注意的是,雖然Java模型和重新建模前 是一樣的,關系模型和持久性語義上仍然存在輕微的區別。
請注意如果你使用<set>標簽,一個組合元素的映射不支持可能為空的屬性. 當刪除對象時, Hibernate必須使用每一個字段的來確定一條記錄(在組合元素表中,沒有單個的關鍵字段), 如果有為null的字段,這樣做就不可能了。你必須作出一個選擇,要么在組合元素中使用不能為空的屬性, 要么選擇使用<list>, <map>,<bag> 或者 <idbag>而不是 <set>。
組合元素有個特別的案例,是組合元素可以包含一個<many-to-one> 元素。類似這樣的映射允許你映射一個many-to-mang關聯表作為組合元素額外的字段。(A mapping like this allows you to map extra columns of a many-to-many association table to the composite element class.) 接下來的的例子是從Order到Item的一個多對多的關聯關系,而 purchaseDate, price 和 quantity 是Item的關聯屬性。
<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的集合中,但它不能同時被Item所關聯。
即使三重或多重管理都是可能的:
<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>
在查詢中,組合元素使用的語法是和關聯到其他實體的語法一樣的。
9.3.?組件作為Map的索引(Components as Map indices )
<composite-map-key>元素允許你映射一個Component類作為Map的key, 但是你必須確定你正確的在這個類中重寫了hashCode() 和 equals()方法。
9.4.?組件作為聯合標識符(Components as composite identifiers)
你可以使用一個component作為一個實體類的標識符。 你的component類必須滿足以下要求:
注意:在Hibernate3中,第二種要求并非是Hibernate強制必須的。但最好這樣做。
你不能使用一個IdentifierGenerator產生組合關鍵字。作為替代應用程序必須分配它自己的標識符。
使用<composite-id> 標簽(并且內嵌<key-property>元素)代替通常的<id>標簽。 比如,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的多對多關聯也使用聯合外鍵:
<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>
9.5.?動態組件 (Dynamic components)
你甚至可以映射Map類型的屬性:
<dynamic-component name="userAttributes">
<property name="foo" column="FOO"/>
<property name="bar" column="BAR"/>
<many-to-one name="baz" class="Baz" column="BAZ_ID"/>
</dynamic-component>
從<dynamic-component>映射的語義上來講,它和<component>是相同的。 這種映射類型的優點在于通過修改映射文件,就可以具有在部署時檢測真實屬性的能力.利用一個DOM解析器,是有可能在運行時刻操作映射文件的。 更好的是,你可以通過Configuration對象來訪問(或者修改)Hibernate的運行時元模型。