Hibernate參考文檔html
3.1.2java
目錄node
前言git
1. 翻譯說明web
2. 版權聲明算法
1.1.前言數據庫
1.2.第一部分-第一個Hibernate應用程序express
1.2.1.第一個classapache
1.4.第三部分 - EventManager web應用程序
3.4.2. 外鏈接抓取(OuterJoin Fetching)
3.4.6. Hibernate的統計(statistics)機制
3.8.3. 在JTA環境下使用Current Session context (當前session上下文)管理
4.1.1. 實現一個默認的(即無參數的)構造方法(constructor)
4.1.2. 提供一個標識屬性(identifierproperty)(可選)
4.1.4. 爲持久化字段聲明訪問器(accessors)和是否可變的標誌(mutators)(可選)
5.對象/關係數據庫映射基礎(BasicO/R Mapping)
5.1.4.2.高/低位算法(Hi/LoAlgorithm)
5.1.4.3.UUID算法(UUID Algorithm )
5.1.4.4. 標識字段和序列(Identitycolumns and Sequences)
5.1.4.5. 程序分配的標識符(AssignedIdentifiers)
5.1.4.6.觸發器實現的主鍵生成器(Primary keys assigned by triggers)
5.1.13.組件(component), 動態組件(dynamic-component)
5.1.16. 鏈接的子類(joined-subclass)
5.1.20.字段和規則元素(column and formula elements)
5.5.2.使用 JDK 5.0 的註解(Annotation)
5.6.數據庫生成屬性(Generated Properties)
5.7.輔助數據庫對象(Auxiliary Database Objects)
6.1.持久化集合類(Persistent collections)
6.2.集合映射( Collection mappings )
6.2.1.集合外鍵(Collection foreign keys)
6.2.2.集合元素(Collection elements)
6.2.3.索引集合類(Indexed collections)
6.2.4.值集合於多對多關聯(Collections of values andmany-to-many associations)
6.2.5.一對多關聯(One-to-many Associations)
6.3.高級集合映射(Advanced collection mappings)
6.3.1.有序集合(Sorted collections)
6.3.2.雙向關聯(Bidirectional associations)
6.3.4.三重關聯(Ternary associations)
7.2.單向關聯(Unidirectional associations)
7.3.使用鏈接表的單向關聯(Unidirectional associations with jointables)
7.4.雙向關聯(Bidirectional associations)
7.4.1.一對多(one to many) / 多對一(many to one)
7.5.使用鏈接表的雙向關聯(Bidirectional associations with jointables)
7.5.1.一對多(one to many)/多對一( many to one)
8.2.在集合中出現的依賴對象 (Collections of dependent objects)
8.3.組件做爲Map的索引(Componentsas Map indices )
8.4.組件做爲聯合標識符(Components as composite identifiers)
9.1.1.每一個類分層結構一張表(Table per class hierarchy)
9.1.2.每一個子類一張表(Table per subclass)
9.1.3. 每一個子類一張表(Tableper subclass),使用辨別標誌(Discriminator)
9.1.4. 混合使用「每一個類分層結構一張表」和「每一個子類一張表」
9.1.5.每一個具體類一張表(Table per concrete class)
9.1.6. Table per concrete class, using implicit polymorphism
10.1.Hibernate對象狀態(object states)
10.4.1.1. 迭代式獲取結果(Iteratingresults)
10.4.1.6. 可滾動遍歷(Scrollableiteration)
10.4.1.7. 外置命名查詢(Externalizingnamed queries)
10.11.傳播性持久化(transitive persistence)
11.1.Session和事務範圍(transaction scope)
11.1.3.關注對象標識(Considering object identity)
11.3.樂觀併發控制(Optimistic concurrency control)
11.3.1. 應用程序級別的版本檢查(Applicationversion checking)
11.3.3. 脫管對象(deatchedobject)和自動版本化
11.4.悲觀鎖定(Pessimistic Locking)
11.5. 鏈接釋放模式(ConnectionRelease Modes)
12.攔截器與事件(Interceptors and events)
13.3.StatelessSession (無狀態session)接口
13.4.DML(數據操做語言)風格的操做(DML-styleoperations)
15.7.投影(Projections)、聚合(aggregation)和分組(grouping)
15.9.根據天然標識查詢(Queries by natural identifier)
16.3.1.使用return-property來明確地指定字段/別名
16.4.定製SQL用來create,update和delete
19.1.抓取策略(Fetching strategies)
19.1.2.調整抓取策略(Tuning fetch strategies)
19.1.3.單端關聯代理(Single-ended association proxies)
19.1.4. 實例化集合和代理(Initializingcollections and proxies)
19.1.5.使用批量抓取(Using batch fetching)
19.1.6. 使用子查詢抓取(Usingsubselect fetching)
19.1.7. 使用延遲屬性抓取(Usinglazy property fetching)
19.2.二級緩存(The Second Level Cache)
19.2.2.策略:只讀緩存(Strategy: read only)
19.2.3.策略:讀/寫緩存(Strategy: read/write)
19.2.4.策略:非嚴格讀/寫緩存(Strategy: nonstrict read/write)
19.2.5. 策略:事務緩存(transactional)
19.3.管理緩存(Managing the caches)
19.5.理解集合性能(Understanding Collection performance)
19.5.2. Lists, maps 和sets用於更新效率最高
19.6.監測性能(Monitoring performance)
20.1.Schema自動生成(Automatic schema generation)
20.1.1.對schema定製化(Customizingthe schema)
20.1.5.對schema的增量更新(Incrementalschema updates)
20.1.6.用Ant來增量更新schema(UsingAnt for incremental schema updates)
21. 示例:父子關係(ParentChild Relationships)
21.2.雙向的一對多關係(Bidirectional one-to-many)
21.3.級聯生命週期(Cascading lifecycle)
21.4.級聯與未保存值(Cascades and unsaved-value)
23.1.Employer(僱主)/Employee(僱員)
23.3. Customer(客戶)/Order(訂單)/Product(產品)
23.4.1. "Typed" one-to-one association
23.4.3. 共有組合鍵屬性的多對多(Many-to-manywith shared composite key attribute)
23.4.4. Content based discrimination
23.4.5. Associations on alternate keys
WARNING! This is a translated versionof the English Hibernate reference documentation. The translated version mightnot be up to date! However, the differences should only be very minor. Consultthe English reference documentation if you are missing information or encountera translation error. If you like to contribute to a particular translation,contact us on the Hibernate developer mailing list.
Translator(s): RedSaga Translate Team滿江紅翻譯團隊 <caoxg@yahoo.com>
在今日的企業環境中,把面向對象的軟件和關係數據庫一塊兒使用多是至關麻煩、浪費時間的。Hibernate是一個面向Java環境的對象/關係數據庫映射工具。對象/關係數據庫映射(object/relational mapping (ORM))這個術語表示一種技術,用來把對象模型表示的對象映射到基於SQL的關係模型數據結構中去。
Hibernate不只僅管理Java類到數據庫表的映射(包括Java數據類型到SQL數據類型的映射),還提供數據查詢和獲取數據的方法,能夠大幅度減小開發時人工使用SQL和JDBC處理數據的時間。
Hibernate的目標是對於開發者一般的數據持久化相關的編程任務,解放其中的95%。對於以數據爲中心的程序來講,它們每每只在數據庫中使用存儲過程來實現商業邏輯,Hibernate可能不是最好的解決方案;對於那些在基於Java的中間層應用中,它們實現面向對象的業務模型和商業邏輯的應用,Hibernate是最有用的。無論怎樣,Hibernate必定能夠幫助你消除或者包裝那些針對特定廠商的SQL代碼,而且幫你把結果集從表格式的表示形式轉換到一系列的對象去。
若是你對Hibernate和對象/關係數據庫映射仍是個新手,或者甚至對Java也不熟悉,請按照下面的步驟來學習。
若是你有問題,請使用Hibernate網站上連接的用戶論壇。咱們也提供一個JIRA問題追蹤系統,來蒐集bug報告和新功能請求。若是你對開發Hibernate有興趣,請加入開發者的郵件列表。(Hibernate網站上的用戶論壇有一箇中文版面,JavaEye也有Hibernate中文版面,您能夠在那裏交流問題與經驗。)
商業開發、產品支持和Hibernate培訓能夠經過JBossInc.得到。(請查閱:http://www.hibernate.org/SupportTraining/)。 Hibernate是一個專業的開放源代碼項目(ProfessionalOpen Source project),也是JBossEnterprise Middleware System(JEMS),JBoss企業級中間件系統的一個核心組件。
本文檔的翻譯是在網絡上協做進行的,也會不斷根據Hibernate的升級進行更新。提供此文檔的目的是爲了減緩學習Hibernate的坡度,而非代替原文檔。咱們建議全部有能力的讀者都直接閱讀英文原文。若您對翻譯有異議,或發現翻譯錯誤,敬請不吝賜教,報告到以下email地址:caoat redsaga.com
Hibernate版本3的翻譯由滿江紅翻譯團隊(RedSagaTranslate Team)集體進行,這也是一次大規模網絡翻譯的試驗。在不到20天的時間內,咱們完成了兩百多頁文檔的翻譯,這一成果是經過十幾位網友集體努力完成的。經過此次翻譯,咱們也有了一套完整的流程,從初譯、技術審覈一直到文字審覈、發佈。咱們的翻譯團隊還會繼續完善咱們的翻譯流程,並翻譯其餘優秀的Java開源資料,敬請期待。
序號 |
標題 |
中文標題 |
v3翻譯 |
v3審校 |
v3.1審校 |
-- |
Quickstart with Tomcat |
在Tomcat中快速上手(3.1版本中取消) |
曹曉鋼 |
zoujm |
-- |
#1 |
Turtotial |
Hibernate入門 |
Zheng Shuai |
- |
Sean Chan |
#2 |
Architecture |
體系結構 |
Hilton(BJUG) |
厭倦發呆 |
Sean Chan |
#3 |
Configuration |
配置 |
Goncha |
mochow |
zcgly |
#4 |
Persistent Classes |
持久化類 |
曹曉鋼 |
mochow |
DigitalSonic |
#5 |
Basic O/R Mapping |
對象/關係數據庫映射基礎(上) |
moxie |
Kingfish |
張成鋼 |
|
|
對象/關係數據庫映射基礎(下) |
inter_dudu |
劉國雄(vincent) |
張成鋼 |
#6 |
Collection Mapping |
集合類映射 |
曹曉鋼 |
robbin |
-- |
#7 |
Association Mappings |
關聯關係映射 |
Robbin |
devils.advocate |
-- |
#8 |
Component Mapping |
組件映射 |
曹曉鋼 |
Robbin |
Song guo qiang |
#9 |
Inheritance Mappings |
繼承映射 |
morning(BJUG) |
mochow |
Liang cheng |
#10 |
Working with objects |
與對象共事 |
程廣楠 |
厭倦發呆 |
-- |
#11 |
Transactions And Concurrency |
事務和併發 |
Robbin |
mochow |
-- |
#12 |
Interceptors and events |
繼承映射 |
七彩狼(BJUG) |
厭倦發呆 |
-- |
#13 |
Batch processing |
批量處理 |
Kingfish(BJUG) |
厭倦發呆 |
-- |
#14 |
HQL: The Hibernate Query Language |
HQL: Hibernate查詢語言 |
鄭浩(BJUG) |
Zheng Shuai |
-- |
#15 |
Criteria Queries |
條件查詢 |
nemo(BJUG) |
Zheng Shuai |
-- |
#16 |
Native SQL |
Native SQL查詢 |
似水流年 |
zoujm |
-- |
#17 |
Filters |
過濾數據 |
冰雲(BJUG) |
Goncha |
-- |
#18 |
XML Mapping |
XML映射 |
edward(BJUG) |
Goncha |
huxb |
#19 |
Improving performance |
性能提高 |
Wangjinfeng |
Robbin |
-- |
#20 |
Toolset Guide |
工具箱指南 |
曹曉鋼 |
Robbin |
-- |
#21 |
Example: Parent/Child |
示例:父子關係 |
曹曉鋼 |
devils.advocate |
-- |
#22 |
Example: Weblog Application |
示例:Weblog 應用程序 |
曹曉鋼 |
devils.advocate |
-- |
#23 |
Example: Various Mappings |
示例:多種映射 |
shidu(BJUG) |
冰雲 |
-- |
#24 |
Best Practices |
最佳實踐 |
曹曉鋼 |
冰雲 |
-- |
關於咱們
滿江紅.開源, http://www.redsaga.com
從成立之初就致力於Java開放源代碼在中國的傳播與發展,與國內多個Java團體及出版社有深刻交流。堅持少說多作的原則,目前有兩個團隊,「OpenDoc團隊」與「翻譯團隊」,本翻譯文檔即爲翻譯團隊做品。OpenDoc團隊已經推出包括Hibernate、iBatis、Spring、WebWork的多份開放文檔,並於2005年5月在Hibernate開放文檔基礎上擴充成書,出版了原創書籍:《深刻淺出Hibernate》,本書400餘頁,適合各個層次的Hibernate用戶。(http://www.redsaga.com/hibernate_book.html)敬請支持。
北京Java用戶組, http://www.bjug.org
BeiingJava User Group,民間技術交流組織,成立於2004年6月。以交流與共享爲宗旨,每兩週舉行一次技術聚會活動。BJUG的目標是,經過小部分人的努力,造成一個技術社羣,建立良好的交流氛圍,並將新的技術和思想推廣到整個IT界,讓咱們共同進步。
Java視線, http://www.javaeye.com
Java視線在是Hibernate中文論壇(http://www.hibernate.org.cn,Hibernate中文論壇是中國最先的Hibernate專業用戶論壇,爲Hibernate在中國的推廣作出了巨大的貢獻)基礎上發展起來的Java深度技術網站,目標是成爲一個高品質的,有思想深度的、原創精神的Java技術交流網站,爲軟件從業人員提供一個自由的交流技術,交流思想和交流信息的平臺。
致謝
還有一些朋友給咱們發來了勘誤,在此致謝:Kurapica,李毅,李海林。
Hibernate英文文檔屬於Hibernate發行包的一部分,遵循LGPL協議。本翻譯版本一樣遵循LGPL協議。參與翻譯的譯者一致贊成放棄除署名權外對本翻譯版本的其它權利要求。
您能夠自由連接、下載、傳播此文檔,或者放置在您的網站上,甚至做爲產品的一部分發行。但前提是必須保證全文完整轉載,包括完整的版權信息和做譯者聲明,並不能違反LGPL協議。這裏「完整」的含義是,不能進行任何刪除/增添/註解。如有刪除/增添/註解,必須逐段明確聲明那些部分並不是本文檔的一部分。
本章是面向Hibernate初學者的一個入門教程。咱們從一個使用駐留內存式(in-memory)數據庫的簡單命令行應用程序開始,用易於理解的方式逐步開發。
本章面向Hibernate初學者,但須要Java和SQL知識。它是在MichaelGoegl所寫的指南的基礎上完成的。在這裏,咱們稱第三方庫文件是指JDK 1.4和5.0。若使用JDK1.3,你可能須要其它的庫文件。
本章的源代碼已包含在發佈包中,位於doc/reference/tutorial/目錄下。
首先咱們將建立一個簡單的基於控制檯的(console-based)Hibernate應用程序。因爲咱們使用Java數據庫(HSQLDB),因此沒必要安裝任何數據庫服務器。
假設咱們但願有一個小應用程序能夠保存咱們但願參加的活動(events)和這些活動主辦方的相關信息。(譯者注:在本教程的後面部分,咱們將直接使用event而不是它的中文翻譯「活動」,以避免混淆。)
咱們所作的第一件事就是建立咱們的開發目錄,而且把全部須要用到的Java庫文件放進去。解壓縮從Hibernate網站下載的Hibernate發佈包,並把/lib目錄下全部須要的庫文件拷到咱們新建開發目錄下的/lib目錄下。看起來就像這樣:
.
+lib
antlr.jar
cglib.jar
asm.jar
asm-attrs.jars
commons-collections.jar
commons-logging.jar
ehcache.jar
hibernate3.jar
jta.jar
dom4j.jar
log4j.jar
到編寫本文時爲止,這些是Hibernate運行所須要的最小庫文件集合(注意咱們也拷貝了 Hibernate3.jar,這個是最主要的文件)。你正使用的Hibernate版本可能須要比這更多或少一些的庫文件。請參見發佈包中的lib/目錄下的README.txt,以獲取更多關於所需和可選的第三方庫文件信息(事實上,Log4j並非必須的庫文件,但被許多開發者所喜歡)。
接下來咱們建立一個類,用來表明那些咱們但願儲存在數據庫裏的event。
咱們的第一個持久化類是一個帶有一些屬性(property)的簡單JavaBean類:
package events;
import java.util.Date;
public class Event {
private Long id;
private String title;
private Date date;
public Event() {}
public Long getId() {
return id;
}
private voidsetId(Long id) {
this.id = id;
}
public Date getDate(){
return date;
}
public voidsetDate(Date date) {
this.date = date;
}
public StringgetTitle() {
return title;
}
public voidsetTitle(String title) {
this.title =title;
}
}
你能夠看到這個類對屬性的存取方法(getter and setter method)使用了標準JavaBean命名約定,同時把類屬性(field)的訪問級別設成私有的(private)。這是推薦的設計,但並非必須的。Hibernate也能夠直接訪問這些field,而使用訪問方法(accessor method)的好處是提供了重構時的健壯性(robustness)。爲了經過反射機制(Reflection)來實例化這個類的對象,咱們須要提供一個無參的構造器(no-argument constructor)。
對一特定的event, id 屬性持有惟一的標識符(identifier)的值。若是咱們但願使用Hibernate提供的全部特性,那麼全部的持久化實體(persistent entity)類(這裏也包括一些次要依賴類)都須要一個這樣的標識符屬性。而事實上,大多數應用程序(特別是web應用程序)都須要經過標識符來區別對象,因此你應該考慮使用標識符屬性而不是把它看成一種限制。然而,咱們一般不會操做對象的標識(identity),所以它的setter方法的訪問級別應該聲明private。這樣當對象被保存的時候,只有Hibernate能夠爲它分配標識符值。你可看到Hibernate能夠直接訪問public,private和protected的訪問方法和field。因此選擇哪一種方式徹底取決於你,你能夠使你的選擇與你的應用程序設計相吻合。
全部的持久化類(persistent classes)都要求有無參的構造器,由於Hibernate必須使用Java反射機制來爲你建立對象。構造器(constructor)的訪問級別能夠是private,然而當生成運行時代理(runtime proxy)的時候則要求使用至少是package級別的訪問控制,這樣在沒有字節碼指令(bytecode instrumentation)的狀況下,從持久化類裏獲取數據會更有效率。
把這個Java源代碼文件放到開發目錄下的src目錄裏,注意包位置要正確。如今這個目錄看起來應該像這樣:
.
+lib
<Hibernate andthird-party libraries>
+src
+events
Event.java
下一步,咱們把這個持久化類的信息告訴Hibernate。
Hibernate須要知道怎樣去加載(load)和存儲(store)持久化類的對象。這正是Hibernate映射文件發揮做用的地方。映射文件告訴Hibernate它,應該訪問數據庫(database)裏面的哪一個表(table)及應該使用表裏面的哪些字段(column)。
一個映射文件的基本結構看起來像這樣:
<?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>
[...]
</hibernate-mapping>
注意Hibernate的DTD是很是複雜的。你的編輯器或者IDE裏使用它來自動完成那些用來映射的XML元素(element)和屬性(attribute)。你也能夠在文本編輯器裏打開DTD-這是最簡單的方式來概覽全部的元素和attribute,並查看它們的缺省值以及註釋。注意Hibernate不會從web加載DTD文件,但它會首先在應用程序的classpath中查找。DTD文件已包括在hibernate3.jar裏,同時也在Hibernate發佈包的src/目錄下。
爲縮短代碼長度,在之後的例子裏咱們會省略DTD的聲明。固然,在實際的應用程序中,DTD聲明是必須的。
在hibernate-mapping標籤(tag)之間, 含有一個class元素。全部的持久化實體類(再次聲明,或許接下來會有依賴類,就是那些次要的實體)都須要一個這樣的映射,來把類對象映射到SQL數據庫裏的表。
<hibernate-mapping>
<classname="events.Event" table="EVENTS">
</class>
</hibernate-mapping>
到目前爲止,咱們告訴了Hibernate怎樣把Events類的對象持久化到數據庫的EVENTS表裏,以及怎樣從EVENTS表加載到Events類的對象。每一個實例對應着數據庫表中的一行。如今咱們將繼續討論有關惟一標識符屬性到數據庫表的映射。另外,因爲咱們不關心怎樣處理這個標識符,咱們就配置由Hibernate的標識符生成策略來產生代理主鍵字段。
<hibernate-mapping>
<classname="events.Event" table="EVENTS">
<idname="id" column="EVENT_ID">
<generatorclass="native"/>
</id>
</class>
</hibernate-mapping>
id元素是標識符屬性的聲明,name="id" 聲明瞭Java屬性的名字- Hibernate會使用getId()和setId()來訪問它。column屬性則告訴Hibernate, 咱們使用EVENTS表的哪一個字段做爲主鍵。嵌套的generator元素指定了標識符生成策略,在這裏咱們指定native,它根據已配置的數據庫(方言)自動選擇最佳的標識符生成策略。Hibernate支持由數據庫生成,全局惟一性(globallyunique)和應用程序指定(或者你本身爲任何已有策略所寫的擴展)這些策略來生成標識符。
最後咱們在映射文件裏面包含須要持久化屬性的聲明。默認狀況下,類裏面的屬性都被視爲非持久化的:
<hibernate-mapping>
<classname="events.Event" table="EVENTS">
<idname="id" column="EVENT_ID">
<generatorclass="native"/>
</id>
<propertyname="date" type="timestamp"column="EVENT_DATE"/>
<propertyname="title"/>
</class>
</hibernate-mapping>
和id元素同樣,property元素的name屬性告訴Hibernate使用哪一個getter和setter方法。在此例中,Hibernate會尋找getDate()/setDate(),以及getTitle()/setTitle()。
爲何date屬性的映射含有column attribute,而title卻沒有?當沒有設定column attribute 的時候,Hibernate缺省地使用JavaBean的屬性名做爲字段名。對於title,這樣工做得很好。然而,date在多數的數據庫裏,是一個保留關鍵字,因此咱們最好把它映射成一個不一樣的名字。
另外一有趣的事情是title屬性缺乏一個type attribute。咱們在映射文件裏聲明並使用的類型,卻不是咱們指望的那樣,是Java數據類型,同時也不是SQL數據庫的數據類型。這些類型就是所謂的Hibernate 映射類型(mapping types),它們能把Java數據類型轉換到SQL數據類型,反之亦然。再次重申,若是在映射文件中沒有設置type屬性的話,Hibernate會本身試着去肯定正確的轉換類型和它的映射類型。在某些狀況下這個自動檢測機制(在Java 類上使用反射機制)不會產生你所期待或須要的缺省值。date屬性就是個很好的例子,Hibernate沒法知道這個屬性(java.util.Date類型的)應該被映射成:SQL date,或timestamp,仍是time 字段。在此例中,把這個屬性映射成timestamp 轉換器,這樣咱們預留了日期和時間的所有信息。
應該把這個映射文件保存爲Event.hbm.xml,且就在EventJava類的源文件目錄下。映射文件可隨意地命名,但hbm.xml的後綴已成爲Hibernate開發者社區的約定。如今目錄結構看起來應該像這樣:
.
+lib
<Hibernate andthird-party libraries>
+src
+events
Event.java
Event.hbm.xml
咱們繼續進行Hibernate的主要配置。
如今咱們已經有了一個持久化類和它的映射文件,該是配置Hibernate的時候了。在此以前,咱們須要一個數據庫。 HSQL DB是種基於Java的SQL數據庫管理系統(DBMS),能夠從HSQL DB的網站上下載。實際上,你只需下載的包中的hsqldb.jar文件,並把這個文件放在開發文件夾的lib/目錄下便可。
在開發的根目錄下建立一個data目錄-這是HSQL DB存儲數據文件的地方。此時在data目錄中運行java -classpath lib/hsqldb.jar org.hsqldb.Server就可啓動數據庫。你能夠在log中看到它的啓動,及綁定到TCP/IP套結字,這正是咱們的應用程序稍後會鏈接的地方。若是你但願在本例中運行一個全新的數據庫,就在窗口中按下CTRL + C來關閉HSQL數據庫,並刪除data/目錄下的全部文件,再從新啓動HSQL數據庫。
Hibernate是你的應用程序裏鏈接數據庫的那層,因此它須要鏈接用的信息。鏈接(connection)是經過一個也由咱們配置的JDBC鏈接池(connection pool)來完成的。Hibernate的發佈包裏包含了許多開源的(opensource)鏈接池,但在咱們例子中使用Hibernate內置的鏈接池。注意,若是你但願使用一個產品級(production-quality)的第三方鏈接池軟件,你必須拷貝所需的庫文件到你的classpath下,並使用不一樣的鏈接池設置。
爲了保存Hibernate的配置,咱們能夠使用一個簡單的hibernate.properties文件,或者一個稍微複雜的hibernate.cfg.xml,甚至能夠徹底使用程序來配置Hibernate。多數用戶更喜歡使用XML配置文件:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Databaseconnection settings -->
<propertyname="connection.driver_class">org.hsqldb.jdbcDriver</property>
<propertyname="connection.url">jdbc:hsqldb:hsql://localhost</property>
<propertyname="connection.username">sa</property>
<propertyname="connection.password"></property>
<!-- JDBCconnection pool (use the built-in) -->
<propertyname="connection.pool_size">1</property>
<!-- SQLdialect -->
<propertyname="dialect">org.hibernate.dialect.HSQLDialect</property>
<!-- EnableHibernate's automatic session context management -->
<propertyname="current_session_context_class">thread</property>
<!-- Disablethe second-level cache -->
<propertyname="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<!-- Echo allexecuted SQL to stdout -->
<propertyname="show_sql">true</property>
<!-- Drop andre-create the database schema on startup -->
<propertyname="hbm2ddl.auto">create</property>
<mappingresource="events/Event.hbm.xml"/>
</session-factory>
</hibernate-configuration>
注意這個XML配置使用了一個不一樣的DTD。在這裏,咱們配置了Hibernate的SessionFactory-一個關聯於特定數據庫全局的工廠(factory)。若是你要使用多個數據庫,就要用多個的<session-factory>,一般把它們放在多個配置文件中(爲了更容易啓動)。
最開始的4個property元素包含必要的JDBC鏈接信息。方言(dialect)的property元素指明Hibernate生成的特定SQL變量。你很快會看到,Hibernate對持久化上下文的自動session管理就會派上用場。打開hbm2ddl.auto選項將自動生成數據庫模式(schema)-直接加入數據庫中。固然這個選項也能夠被關閉(經過去除這個配置選項)或者經過Ant任務SchemaExport的幫助來把數據庫schema重定向到文件中。最後,在配置中爲持久化類加入映射文件。
把這個文件拷貝到源代碼目錄下面,這樣它就位於classpath的根目錄的最後。Hibernate在啓動時會自動在classpath的根目錄查找名爲hibernate.cfg.xml的配置文件。
如今咱們用Ant來構建應用程序。你必須先安裝Ant-能夠從Ant 下載頁面獲得它。怎樣安裝Ant就不在這裏介紹了,請參考Ant 用戶手冊。當你安裝完了Ant,就能夠開始建立build.xml文件,把它直接放在開發目錄下面。
一個簡單的build文件看起來像這樣:
<project name="hibernate-tutorial"default="compile">
<propertyname="sourcedir" value="${basedir}/src"/>
<propertyname="targetdir" value="${basedir}/bin"/>
<propertyname="librarydir" value="${basedir}/lib"/>
<pathid="libraries">
<filesetdir="${librarydir}">
<includename="*.jar"/>
</fileset>
</path>
<targetname="clean">
<deletedir="${targetdir}"/>
<mkdirdir="${targetdir}"/>
</target>
<targetname="compile" depends="clean, copy-resources">
<javacsrcdir="${sourcedir}"
destdir="${targetdir}"
classpathref="libraries"/>
</target>
<targetname="copy-resources">
<copytodir="${targetdir}">
<filesetdir="${sourcedir}">
<exclude name="**/*.java"/>
</fileset>
</copy>
</target>
</project>
這將告訴Ant把全部在lib目錄下以.jar結尾的文件拷貝到classpath中以供編譯之用。它也把全部的非Java源代碼文件,例如配置和Hibernate映射文件,拷貝到目標目錄。若是你如今運行Ant,會獲得如下輸出:
C:\hibernateTutorial\>ant
Buildfile: build.xml
copy-resources:
[copy] Copying 2files to C:\hibernateTutorial\bin
compile:
[javac] Compiling 1source file to C:\hibernateTutorial\bin
BUILD SUCCESSFUL
Total time: 1 second
是時候來加載和儲存一些Event對象了,但首先咱們得編寫一些基礎的代碼以完成設置。咱們必須啓動Hibernate,此過程包括建立一個全局的SessoinFactory,並把它儲存在應用程序代碼容易訪問的地方。SessionFactory能夠建立並打開新的Session。一個Session表明一個單線程的單元操做,SessionFactory則是個線程安全的全局對象,只須要被實例化一次。
咱們將建立一個HibernateUtil輔助類(helper class)來負責啓動Hibernate和更方便地操做SessionFactory。讓咱們來看一下它的實現:
package util;
import org.hibernate.*;
import org.hibernate.cfg.*;
public class HibernateUtil {
private static finalSessionFactory sessionFactory;
static {
try {
// Create theSessionFactory from hibernate.cfg.xml
sessionFactory= new Configuration().configure().buildSessionFactory();
} catch (Throwableex) {
// Make sureyou log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." +ex);
throw newExceptionInInitializerError(ex);
}
}
public staticSessionFactory getSessionFactory() {
returnsessionFactory;
}
}
這個類不但在它的靜態初始化過程(僅當加載這個類的時候被JVM執行一次)中產生全局的SessionFactory,並且隱藏了它使用了靜態singleton的事實。它也可能在應用程序服務器中的JNDI查找SessionFactory。
若是你在配置文件中給SessionFactory一個名字,在SessionFactory建立後,Hibernate會試着把它綁定到JNDI。要徹底避免這樣的代碼,你也能夠使用JMX部署,讓具備JMX能力的容器來實例化HibernateService並把它綁定到JNDI。這些高級可選項在後面的章節中會討論到。
把HibernateUtil.java放在開發目錄的源代碼路徑下,與放events的包並列:
.
+lib
<Hibernate andthird-party libraries>
+src
+events
Event.java
Event.hbm.xml
+util
HibernateUtil.java
hibernate.cfg.xml
+data
build.xml
再次編譯這個應用程序應該不會有問題。最後咱們須要配置一個日誌(logging)系統- Hibernate使用通用日誌接口,容許你在Log4j和JDK 1.4 日誌之間進行選擇。多數開發者更喜歡Log4j:從Hibernate的發佈包中(它在etc/目錄下)拷貝log4j.properties到你的src目錄,與hibernate.cfg.xml.放在一塊兒。看一下配置示例,若是你但願看到更加詳細的輸出信息,你能夠修改配置。默認狀況下,只有Hibernate的啓動信息纔會顯示在標準輸出上。
示例的基本框架完成了-如今咱們能夠用Hibernate來作些真正的工做。
咱們終於能夠使用Hibernate來加載和存儲對象了,編寫一個帶有main()方法的EventManager類:
package events;
import org.hibernate.Session;
import java.util.Date;
import util.HibernateUtil;
public class EventManager {
public static voidmain(String[] args) {
EventManager mgr =new EventManager();
if(args[0].equals("store")) {
mgr.createAndStoreEvent("My Event", new Date());
}
HibernateUtil.getSessionFactory().close();
}
private voidcreateAndStoreEvent(String title, Date theDate) {
Session session =HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Event theEvent =new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
session.save(theEvent);
session.getTransaction().commit();
}
}
咱們建立了個新的Event對象並把它傳遞給Hibernate。如今Hibernate負責與SQL打交道,並把INSERT命令傳給數據庫。在運行以前,讓咱們看一下處理Session和Transaction的代碼。
一個Session就是個單一的工做單元。咱們暫時讓事情簡單一些,並假設HibernateSession和數據庫事務是一一對應的。爲了讓咱們的代碼從底層的事務系統中脫離出來(此例中是JDBC,但也多是JTA),咱們使用HibernateSession中的Transaction API。
sessionFactory.getCurrentSession()是幹什麼的呢?首先,只要你持有SessionFactory(幸好咱們有HibernateUtil,能夠隨時得到),大可在任什麼時候候、任何地點調用這個方法。getCurrentSession()方法總會返回「當前的」工做單元。記得咱們在hibernate.cfg.xml中把這一配置選項調整爲"thread"了嗎?所以,當前工做單元的範圍就是當前執行咱們應用程序的Java線程。可是,這並不是老是正確的。 Session在第一次被使用的時候,或者第一次調用getCurrentSession()的時候,其生命週期就開始。而後它被Hibernate綁定到當前線程。當事務結束的時候,無論是提交仍是回滾,Hibernate也會把Session從當前線程剝離,而且關閉它。倘若你再次調用getCurrentSession(),你會獲得一個新的Session,而且開始一個新的工做單元。這種線程綁定(thread-bound)的編程模型(model)是使用Hibernate的最普遍的方式。
關於事務處理及事務邊界界定的詳細信息,請參看第 11 章 事務和併發。在上面的例子中,咱們也忽略了全部的錯誤與回滾的處理。
爲第一次運行咱們的程序,咱們得在Ant的build文件中增長一個能夠調用獲得的target。
<target name="run" depends="compile">
<javafork="true" classname="events.EventManager"classpathref="libraries">
<classpathpath="${targetdir}"/>
<argvalue="${action}"/>
</java>
</target>
action參數(argument)的值是經過命令行調用這個target的時候設置的:
C:\hibernateTutorial\>ant run -Daction=store
你應該會看到,編譯之後,Hibernate根據你的配置啓動,併產生一大堆的輸出日誌。在日誌最後你會看到下面這行:
[java] Hibernate: insert into EVENTS (EVENT_DATE, title,EVENT_ID) values (?, ?, ?)
這是Hibernate執行的INSERT命令,問號表明JDBC的綁定參數。若是想要看到綁定參數的值或者減小日誌的長度,就要調整你在log4j.properties文件裏的設置。
咱們想要列出全部已經被存儲的events,就要增長一個條件分支選項到main方法中去。
if (args[0].equals("store")) {
mgr.createAndStoreEvent("My Event", new Date());
}
else if (args[0].equals("list")) {
List events =mgr.listEvents();
for (int i = 0; i <events.size(); i++) {
Event theEvent =(Event) events.get(i);
System.out.println("Event: " + theEvent.getTitle() +
" Time: " + theEvent.getDate());
}
}
咱們也增長一個新的listEvents()方法:
private List listEvents() {
Session session =HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
List result =session.createQuery("from Event").list();
session.getTransaction().commit();
return result;
}
咱們在這裏是用一個HQL(HibernateQuery Language-Hibernate查詢語言)查詢語句來從數據庫中加載全部存在的Event對象。Hibernate會生成適當的SQL,把它發送到數據庫,並操做從查詢獲得數據的Event對象。固然,你能夠使用HQL來建立更加複雜的查詢。
如今,根據如下步驟來執行並測試以上各項:
若是你如今使用命令行參數-Daction=list運行Ant,你會看到那些至今爲止咱們所儲存的events。固然,你也能夠多調用幾回store以保存更多的envents。
注意,不少Hibernate新手在這一步會失敗,咱們不時看到關於Table not found錯誤信息的提問。可是,只要你根據上面描述的步驟來執行,就不會有這個問題,由於hbm2ddl會在第一次運行的時候建立數據庫schema,後繼的應用程序重起後還能繼續使用這個schema。倘若你修改了映射,或者修改了數據庫schema,你必須把hbm2ddl從新打開一次。
咱們已經映射了一個持久化實體類到表上。讓咱們在這個基礎上增長一些類之間的關聯。首先咱們往應用程序裏增長人(people)的概念,並存儲他們所參與的一個Event列表。(譯者注:與Event同樣,咱們在後面將直接使用person來表示「人」而不是它的中文翻譯)
最初簡單的Person類:
package events;
public class Person {
private Long id;
private int age;
private Stringfirstname;
private Stringlastname;
public Person() {}
// Accessor methodsfor all properties, private setter for 'id'
}
建立一個名爲Person.hbm.xml的新映射文件(別忘了最上面的DTD引用):
<hibernate-mapping>
<classname="events.Person" table="PERSON">
<idname="id" column="PERSON_ID">
<generatorclass="native"/>
</id>
<propertyname="age"/>
<propertyname="firstname"/>
<propertyname="lastname"/>
</class>
</hibernate-mapping>
最後,把新的映射加入到Hibernate的配置中:
<mapping resource="events/Event.hbm.xml"/>
<mapping resource="events/Person.hbm.xml"/>
如今咱們在這兩個實體之間建立一個關聯。顯然,persons能夠參與一系列events,而events也有不一樣的參加者(persons)。咱們須要處理的設計問題是關聯方向(directionality),階數(multiplicity)和集合(collection)的行爲。
咱們將向Person類增長一連串的events。那樣,經過調用aPerson.getEvents(),就能夠輕鬆地導航到特定person所參與的events,而不用去執行一個顯式的查詢。咱們使用Java的集合類(collection):Set,由於set 不包含重複的元素及與咱們無關的排序。
咱們須要用set 實現一個單向多值關聯。讓咱們在Java類裏爲這個關聯編碼,接着映射它:
public class Person {
private Set events =new HashSet();
public Set getEvents(){
return events;
}
public voidsetEvents(Set events) {
this.events =events;
}
}
在映射這個關聯以前,先考慮一下此關聯的另一端。很顯然,咱們能夠保持這個關聯是單向的。或者,咱們能夠在Event裏建立另一個集合,若是但願可以雙向地導航,如:anEvent.getParticipants()。從功能的角度來講,這並非必須的。由於你總能夠顯式地執行一個查詢,以得到某個特定event的全部參與者。這是個在設計時須要作出的選擇,徹底由你來決定,但此討論中關於關聯的階數是清楚的:即兩端都是「多」值的,咱們把它叫作多對多(many-to-many)關聯。於是,咱們使用Hibernate的多對多映射:
<class name="events.Person"table="PERSON">
<idname="id" column="PERSON_ID">
<generatorclass="native"/>
</id>
<propertyname="age"/>
<propertyname="firstname"/>
<propertyname="lastname"/>
<setname="events" table="PERSON_EVENT">
<keycolumn="PERSON_ID"/>
<many-to-manycolumn="EVENT_ID" class="events.Event"/>
</set>
</class>
Hibernate支持各類各樣的集合映射,<set>使用的最爲廣泛。對於多對多關聯(或叫n:m實體關係), 須要一個關聯表(associationtable)。表裏面的每一行表明從person到event的一個關聯。表名是由set元素的table屬性配置的。關聯裏面的標識符字段名,對於person的一端,是由<key>元素定義,而event一端的字段名是由<many-to-many>元素的column屬性定義。你也必須告訴Hibernate集合中對象的類(也就是位於這個集合所表明的關聯另一端的類)。
於是這個映射的數據庫schema是:
_____________ __________________
| | | | _____________
| EVENTS | | PERSON_EVENT | | |
|_____________| |__________________| | PERSON |
| | | | |_____________|
| *EVENT_ID | <--> | *EVENT_ID | | |
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID |
| TITLE | |__________________| | AGE |
|_____________| | FIRSTNAME |
| LASTNAME |
|_____________|
咱們把一些people和events一塊兒放到EventManager的新方法中:
private void addPersonToEvent(Long personId, Long eventId) {
Session session =HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson =(Person) session.load(Person.class, personId);
Event anEvent =(Event) session.load(Event.class, eventId);
aPerson.getEvents().add(anEvent);
session.getTransaction().commit();
}
在加載一Person和Event後,使用普通的集合方法就可容易地修改咱們定義的集合。如你所見,沒有顯式的update()或save(),Hibernate會自動檢測到集合已經被修改並須要更新回數據庫。這叫作自動髒檢查(automatic dirty checking),你也能夠嘗試修改任何對象的name或者date屬性,只要他們處於持久化狀態,也就是被綁定到某個Hibernate 的Session上(如:他們剛剛在一個單元操做被加載或者保存),Hibernate監視任何改變並在後臺隱式寫的方式執行SQL。同步內存狀態和數據庫的過程,一般只在單元操做結束的時候發生,稱此過程爲清理緩存(flushing)。在咱們的代碼中,工做單元由數據庫事務的提交(或者回滾)來結束——這是由CurrentSessionContext類的thread配置選項定義的。
固然,你也能夠在不一樣的單元操做裏面加載person和event。或在Session之外修改不是處在持久化(persistent)狀態下的對象(若是該對象之前曾經被持久化,那麼咱們稱這個狀態爲脫管(detached))。你甚至能夠在一個集合被脫管時修改它:
private void addPersonToEvent(Long personId, Long eventId) {
Session session =HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson =(Person) session
.createQuery("select p from Person p left join fetch p.events wherep.id = :pid")
.setParameter("pid", personId)
.uniqueResult(); // Eager fetch the collection so we can use it detached
Event anEvent =(Event) session.load(Event.class, eventId);
session.getTransaction().commit();
// End of first unitof work
aPerson.getEvents().add(anEvent); // aPerson (and its collection) isdetached
// Begin second unitof work
Session session2 =HibernateUtil.getSessionFactory().getCurrentSession();
session2.beginTransaction();
session2.update(aPerson); // Reattachment of aPerson
session2.getTransaction().commit();
}
對update的調用使一個脫管對象從新持久化,你能夠說它被綁定到一個新的單元操做上,因此在脫管狀態下對它所作的任何修改都會被保存到數據庫裏。這也包括你對這個實體對象的集合所做的任何改動(增長/刪除)。
這對咱們當前的情形不是頗有用,但它是很是重要的概念,你能夠把它融入到你本身的應用程序設計中。在EventManager的main方法中添加一個新的動做,並從命令行運行它來完成咱們所作的練習。若是你須要person及event的標識符—那就用save()方法返回它(你可能須要修改前面的一些方法來返回那個標識符):
else if (args[0].equals("addpersontoevent")) {
Long eventId =mgr.createAndStoreEvent("My Event", new Date());
Long personId =mgr.createAndStorePerson("Foo", "Bar");
mgr.addPersonToEvent(personId, eventId);
System.out.println("Added person " + personId + " toevent " + eventId);
上面是個關於兩個同等重要的實體類間關聯的例子。像前面所提到的那樣,在特定的模型中也存在其它的類和類型,這些類和類型一般是「次要的」。你已看到過其中的一些,像int或String。咱們稱這些類爲值類型(value type),它們的實例依賴(depend)在某個特定的實體上。這些類型的實例沒有它們本身的標識(identity),也不能在實體間被共享(好比,兩個person不能引用同一個firstname對象,即便他們有相同的first name)。固然,值類型並不只僅在JDK中存在(事實上,在一個Hibernate應用程序中,全部的JDK類都被視爲值類型),並且你也能夠編寫你本身的依賴類,例如Address,MonetaryAmount。
你也能夠設計一個值類型的集合,這在概念上與引用其它實體的集合有很大的不一樣,可是在Java裏面看起來幾乎是同樣的。
咱們把一個值類型對象的集合加入Person實體中。咱們但願保存email地址,因此使用String類型,並且此次的集合類型又是Set:
private Set emailAddresses = new HashSet();
public Set getEmailAddresses() {
return emailAddresses;
}
public void setEmailAddresses(Set emailAddresses) {
this.emailAddresses =emailAddresses;
}
這個Set的映射
<set name="emailAddresses" table="PERSON_EMAIL_ADDR">
<keycolumn="PERSON_ID"/>
<elementtype="string" column="EMAIL_ADDR"/>
</set>
比較此次和此前映射的差異,主要在於element部分,此次並無包含對其它實體引用的集合,而是元素類型爲String的集合(在映射中使用小寫的名字」string「是向你代表它是一個Hibernate的映射類型或者類型轉換器)。和以前同樣,set元素的table屬性決定了用於集合的表名。key元素定義了在集合表中外鍵的字段名。element元素的column屬性定義用於實際保存String值的字段名。
看一下修改後的數據庫schema。
_____________ __________________
| | | | _____________
| EVENTS | | PERSON_EVENT | | | ___________________
|_____________| |__________________| | PERSON | | |
| | | | |_____________| | PERSON_EMAIL_ADDR |
| *EVENT_ID | <--> | *EVENT_ID | | | |___________________|
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | <--> | *PERSON_ID |
| TITLE | |__________________| | AGE | | *EMAIL_ADDR |
|_____________| | FIRSTNAME | |___________________|
| LASTNAME |
|_____________|
你能夠看到集合表的主鍵其實是個複合主鍵,同時使用了2個字段。這也暗示了對於同一個person不能有重複的email地址,這正是Java裏面使用Set時候所須要的語義(Set裏元素不能重複)。
你如今能夠試着把元素加入到這個集合,就像咱們在以前關聯person和event的那樣。其實現的Java代碼是相同的:
private void addEmailToPerson(Long personId, StringemailAddress) {
Session session =HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson =(Person) session.load(Person.class, personId);
// ThegetEmailAddresses() might trigger a lazy load of the collection
aPerson.getEmailAddresses().add(emailAddress);
session.getTransaction().commit();
}
此次咱們沒有使用fetch查詢來初始化集合。所以,調用其getter方法會觸發另外一附加的select來初始化集合,這樣咱們才能把元素添加進去。檢查SQL log,試着經過預先抓取來優化它。
接下來咱們將映射雙向關聯(bi-directional association)-在Java裏讓person和event能夠從關聯的任何一端訪問另外一端。固然,數據庫schema沒有改變,咱們仍然須要多對多的階數。一個關係型數據庫要比網絡編程語言更加靈活,因此它並不須要任何像導航方向(navigation direction)的東西-數據能夠用任何可能的方式進行查看和獲取。
首先,把一個參與者(person)的集合加入Event類中:
private Set participants = new HashSet();
public Set getParticipants() {
return participants;
}
public void setParticipants(Set participants) {
this.participants =participants;
}
在Event.hbm.xml裏面也映射這個關聯。
<set name="participants"table="PERSON_EVENT" inverse="true">
<keycolumn="EVENT_ID"/>
<many-to-manycolumn="PERSON_ID" class="events.Person"/>
</set>
如你所見,兩個映射文件裏都有普通的set映射。注意在兩個映射文件中,互換了key和many-to-many的字段名。這裏最重要的是Event映射文件裏增長了set元素的inverse="true"屬性。
這意味着在須要的時候,Hibernate能在關聯的另外一端- Person類獲得兩個實體間關聯的信息。這將會極大地幫助你理解雙向關聯是如何在兩個實體間被建立的。
首先請記住,Hibernate並不影響一般的Java語義。在單向關聯的例子中,咱們是怎樣在Person和Event之間建立聯繫的?咱們把Event實例添加到Person實例內的event引用集合裏。所以很顯然,若是咱們要讓這個關聯能夠雙向地工做,咱們須要在另一端作一樣的事情-把Person實例加入Event類內的Person引用集合。這「在關聯的兩端設置聯繫」是徹底必要的並且你都得這麼作。
許多開發人員防護式地編程,建立管理關聯的方法來保證正確的設置了關聯的兩端,好比在Person裏:
protected Set getEvents() {
return events;
}
protected void setEvents(Set events) {
this.events = events;
}
public void addToEvent(Event event) {
this.getEvents().add(event);
event.getParticipants().add(this);
}
public void removeFromEvent(Event event) {
this.getEvents().remove(event);
event.getParticipants().remove(this);
}
注意如今對於集合的get和set方法的訪問級別是protected - 這容許在位於同一個包(package)中的類以及繼承自這個類的子類能夠訪問這些方法,但禁止其餘任何人的直接訪問,避免了集合內容的混亂。你應儘量地在另外一端也把集合的訪問級別設成protected。
inverse映射屬性究竟表示什麼呢?對於你和Java來講,一個雙向關聯僅僅是在兩端簡單地正確設置引用。然而,Hibernate並無足夠的信息去正確地執行INSERT和UPDATE語句(以免違反數據庫約束),因此它須要一些幫助來正確的處理雙向關聯。把關聯的一端設置爲inverse將告訴Hibernate忽略關聯的這一端,把這端當作是另一端的一個鏡象(mirror)。這就是所需的所有信息,Hibernate利用這些信息來處理把一個有嚮導航模型轉移到數據庫schema時的全部問題。你只須要記住這個直觀的規則:全部的雙向關聯須要有一端被設置爲inverse。在一對多關聯中它必須是表明多(many)的那端。而在多對多(many-to-many)關聯中,你能夠任意選取一端,由於兩端之間並無差異。
讓咱們把進入一個小型的web應用程序。
1.4. 第三部分 - EventManager web應用程序
Hibernate web應用程序使用Session 和Transaction的方式幾乎和獨立應用程序是同樣的。可是,有一些常見的模式(pattern)很是有用。如今咱們編寫一個EventManagerServlet。這個servlet能夠列出數據庫中保存的全部的events,還提供一個HTML表單來增長新的events。
在你的源代碼目錄的events包中建立一個新的類:
package events;
// Imports
public class EventManagerServlet extends HttpServlet {
private finalSimpleDateFormat dateFormatter =
new SimpleDateFormat("dd.MM.yyyy");
// Servlet code
}
咱們後面會用到dateFormatter 的工具,它把Date對象轉換爲字符串。只要一個formatter做爲servlet的成員就能夠了。
這個servlet只處理HTTP GET 請求,所以,咱們要實現的是doGet()方法:
protected void doGet(HttpServletRequest request,
HttpServletResponseresponse)
throwsServletException, IOException {
try {
// Begin unit ofwork
HibernateUtil.getSessionFactory()
.getCurrentSession().beginTransaction();
// Process requestand render page...
// End unit ofwork
HibernateUtil.getSessionFactory()
.getCurrentSession().getTransaction().commit();
} catch (Exception ex){
HibernateUtil.getSessionFactory()
.getCurrentSession().getTransaction().rollback();
throw newServletException(ex);
}
}
咱們稱這裏應用的模式爲每次請求一個session(session-per-request)。當有請求到達這個servlet的時候,經過對SessionFactory的第一次調用,打開一個新的Hibernate Session。而後啓動一個數據庫事務—全部的數據訪問都是在事務中進行,無論是讀仍是寫(咱們在應用程序中不使用auto-commit模式)。
下一步,對請求的可能動做進行處理,渲染出反饋的HTML。咱們很快就會涉及到那部分。
最後,當處理與渲染都結束的時候,這個工做單元就結束了。倘若在處理或渲染的時候有任何錯誤發生,會拋出一個異常,回滾數據庫事務。這樣,session-per-request模式就完成了。爲了不在每一個servlet中都編寫事務邊界界定的代碼,能夠考慮寫一個servlet 過濾器(filter)來更好地解決。關於這一模式的更多信息,請參閱Hibernate網站和Wiki,這一模式叫作Open Session in View—只要你考慮用JSP來渲染你的視圖(view),而不是在servlet中,你就會很快用到它。
咱們來實現處理請求以及渲染頁面的工做。
// Write HTML header
PrintWriter out = response.getWriter();
out.println("<html><head><title>EventManager</title></head><body>");
// Handle actions
if ("store".equals(request.getParameter("action")) ) {
String eventTitle =request.getParameter("eventTitle");
String eventDate =request.getParameter("eventDate");
if ("".equals(eventTitle) || "".equals(eventDate) ) {
out.println("<b><i>Please enter event title anddate.</i></b>");
} else {
createAndStoreEvent(eventTitle, dateFormatter.parse(eventDate));
out.println("<b><i>Addedevent.</i></b>");
}
}
// Print page
printEventForm(out);
listEvents(out);
// Write HTML footer
out.println("</body></html>");
out.flush();
out.close();
必須認可,這種編碼風格讓Java與HTML混合在了一塊兒,在更復雜的應用程序中不該大量地使用—記住咱們在章中僅爲了展現Hibernate的基本概念。這段代碼打印出了HTML頭和尾部。在頁面中,打印出一個輸入event條目的表單,並列出數據庫中全部events。第一個方法微不足道,僅僅是輸出HTML:
private void printEventForm(PrintWriter out) {
out.println("<h2>Add new event:</h2>");
out.println("<form>");
out.println("Title: <input name='eventTitle'length='50'/><br/>");
out.println("Date(e.g. 24.12.2009): <input name='eventDate' length='10'/><br/>");
out.println("<input type='submit' name='action'value='store'/>");
out.println("</form>");
}
listEvents()方法使用綁定到當前線程的Hibernate Session來執行查詢:
private void listEvents(PrintWriter out) {
List result =HibernateUtil.getSessionFactory()
.getCurrentSession().createCriteria(Event.class).list();
if (result.size() >0) {
out.println("<h2>Events in database:</h2>");
out.println("<table border='1'>");
out.println("<tr>");
out.println("<th>Event title</th>");
out.println("<th>Event date</th>");
out.println("</tr>");
for (Iterator it =result.iterator(); it.hasNext();) {
Event event =(Event) it.next();
out.println("<tr>");
out.println("<td>" + event.getTitle() +"</td>");
out.println("<td>" +dateFormatter.format(event.getDate()) + "</td>");
out.println("</tr>");
}
out.println("</table>");
}
}
最後,store動做會被導向到createAndStoreEvent()方法,它也使用當前線程的Session:
protected void createAndStoreEvent(String title, Date theDate) {
Event theEvent = newEvent();
theEvent.setTitle(title);
theEvent.setDate(theDate);
HibernateUtil.getSessionFactory()
.getCurrentSession().save(theEvent);
}
大功告成,這個servlet寫完了。Hibernate會在單一的Session 和Transaction中處理到達的servlet請求。如同在前面的獨立應用程序中那樣,Hibernate能夠自動的把這些對象綁定到當前運行的線程中。這給了你用任何你喜歡的方式來對代碼分層及訪問SessionFactory的自由。一般,你會用更加完備的設計,把數據訪問代碼轉移到數據訪問對象中(DAO模式)。請參見HibernateWiki,那裏有更多的例子。
要發佈這個程序,你得把它打成web發佈包:WAR文件。把下面的腳本加入到你的build.xml中:
<target name="war" depends="compile">
<wardestfile="hibernate-tutorial.war" webxml="web.xml">
<libdir="${librarydir}">
<excludename="jsdk*.jar"/>
</lib>
<classesdir="${targetdir}"/>
</war>
</target>
這段代碼在你的開發目錄中建立一個hibernate-tutorial.war的文件。它把全部的類庫和web.xml描述文件都打包進去,web.xml 文件應該位於你的開發根目錄中:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2eehttp://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>Event Manager</servlet-name>
<servlet-class>events.EventManagerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Event Manager</servlet-name>
<url-pattern>/eventmanager</url-pattern>
</servlet-mapping>
</web-app>
請注意在你編譯和部署web應用程以前,須要一個附加的類庫:jsdk.jar。這是JavaServlet開發包,倘若你尚未,能夠從Sun網站上下載,把它copy到你的lib目錄。可是,它僅僅是在編譯時須要,不會被打入WAR包。
在你的開發目錄中,調用ant war來構建、打包,而後把hibernate-tutorial.war文件拷貝到你的tomcat的webapps目錄下。倘若你還沒安裝Tomcat,就去下載一個,按照指南來安裝。對此應用的發佈,你不須要修改任何Tomcat的配置。
在部署完,啓動Tomcat以後,經過http://localhost:8080/hibernate-tutorial/eventmanager進行訪問你的應用,在第一次servlet 請求發生時,請在Tomcatlog中確認你看到Hibernate被初始化了(HibernateUtil的靜態初始化器被調用),倘若有任何異常拋出,也能夠看到詳細的輸出。
本章覆蓋瞭如何編寫一個簡單獨立的Hibernate命令行應用程序及小型的Hibernateweb應用程序的基本要素。
若是你已經對Hibernate感到自信,經過開發指南目錄,繼續瀏覽你感興趣的內容-那些會被問到的問題大可能是事務處理 (第 11 章 事務和併發),抓取(fetch)的效率 (第 19 章 提高性能 ),或者API的使用 (第 10 章 與對象共事)和查詢的特性(第 10.4 節「查詢」)。
別忘了去Hibernate的網站查看更多(有針對性的)示例。
一個很是簡要的Hibernate體系結構的概要圖:
從這個圖能夠看出,Hibernate使用數據庫和配置信息來爲應用程序提供持久化服務(以及持久的對象)。
咱們來更詳細地看一下Hibernate運行時體系結構。因爲Hibernate很是靈活,且支持多種應用方案,因此咱們這隻描述一下兩種極端的狀況。「輕型」的體系結構方案,要求應用程序提供本身的JDBC 鏈接並管理本身的事務。這種方案使用了Hibernate API的最小子集:
「全面解決」的體系結構方案,將應用層從底層的JDBC/JTA API中抽象出來,而讓Hibernate來處理這些細節。
圖中各個對象的定義以下:
SessionFactory (org.hibernate.SessionFactory)
針對單個數據庫映射關係通過編譯後的內存鏡像,是線程安全的(不可變)。它是生成Session的工廠,自己要用到ConnectionProvider。該對象能夠在進程或集羣的級別上,爲那些事務之間能夠重用的數據提供可選的二級緩存。
Session (org.hibernate.Session)
表示應用程序與持久儲存層之間交互操做的一個單線程對象,此對象生存期很短。其隱藏了JDBC鏈接,也是Transaction的工廠。其會持有一個針對持久化對象的必選(第一級)緩存,在遍歷對象圖或者根據持久化標識查找對象時會用到。
持久的對象及其集合
帶有持久化狀態的、具備業務功能的單線程對象,此對象生存期很短。這些對象多是普通的JavaBeans/POJO,惟一特殊的是他們正與(僅僅一個)Session相關聯。一旦這個Session被關閉,這些對象就會脫離持久化狀態,這樣就可被應用程序的任何層自由使用。(例如,用做跟表示層打交道的數據傳輸對象。)
瞬態(transient)和脫管(detached)的對象及其集合
那些目前沒有與session關聯的持久化類實例。他們多是在被應用程序實例化後,還沒有進行持久化的對象。也多是由於實例化他們的Session已經被關閉而脫離持久化的對象。
事務Transaction (org.hibernate.Transaction)
(可選的)應用程序用來指定原子操做單元範圍的對象,它是單線程的,生命週期很短。它經過抽象將應用從底層具體的JDBC、JTA以及CORBA事務隔離開。某些狀況下,一個Session以內可能包含多個Transaction對象。儘管是否使用該對象是可選的,但不管是使用底層的API仍是使用Transaction對象,事務邊界的開啓與關閉是必不可少的。
ConnectionProvider (org.hibernate.connection.ConnectionProvider)
(可選的)生成JDBC鏈接的工廠(同時也起到鏈接池的做用)。它經過抽象將應用從底層的Datasource或DriverManager隔離開。僅供開發者擴展/實現用,並不暴露給應用程序使用。
TransactionFactory (org.hibernate.TransactionFactory)
(可選的)生成Transaction對象實例的工廠。僅供開發者擴展/實現用,並不暴露給應用程序使用。
擴展接口
Hibernate提供了不少可選的擴展接口,你能夠經過實現它們來定製你的持久層的行爲。具體請參考API文檔。
在特定「輕型」的體系結構中,應用程序可能繞過 Transaction/TransactionFactory 以及 ConnectionProvider 等API直接跟JTA或JDBC打交道。
一個持久化類的實例可能處於三種不一樣狀態中的某一種。這三種狀態的定義則與所謂的持久化上下文(persistence context)有關。 Hibernate的Session對象就是這個所謂的持久化上下文:
瞬態(transient)
該實例從未與任何持久化上下文關聯過。它沒有持久化標識(至關於主鍵值)。
持久化(persistent)
實例目前與某個持久化上下文有關聯。它擁有持久化標識(至關於主鍵值),而且可能在數據庫中有一個對應的行。對於某一個特定的持久化上下文,Hibernate保證持久化標識與Java標識(其值表明對象在內存中的位置)等價。
脫管(detached)
實例曾經與某個持久化上下文發生過關聯,不過那個上下文被關閉了,或者這個實例是被序列化(serialize)到另外的進程。它擁有持久化標識,而且在數據庫中可能存在一個對應的行。對於脫管狀態的實例,Hibernate不保證任何持久化標識和Java標識的關係。
JMX是管理Java組件(Javacomponents)的J2EE標準。Hibernate 能夠經過一個JMX標準服務來管理。在這個發行版本中,咱們提供了一個MBean接口的實現,即 org.hibernate.jmx.HibernateService。
想要看如何在JBoss應用服務器上將Hibernate部署爲一個JMX服務的例子,您能夠參考JBoss用戶指南。咱們如今說一下在Jboss應用服務器上,使用JMX來部署Hibernate的好處:
這些選項更多的描述,請參考JBoss 應用程序用戶指南。
將Hibernate以部署爲JMX服務的另外一個好處,是能夠查看Hibernate的運行時統計信息。參看 第 3.4.6 節「 Hibernate的統計(statistics)機制」.
Hibernate也能夠被配置爲一個JCA鏈接器(JCAconnector)。更多信息請參看網站。請注意,Hibernate對JCA的支持,仍處於實驗性階段。
2.5. 上下文相關的(Contextual)Session
使用Hibernate的大多數應用程序須要某種形式的「上下文相關的」 session,特定的session在整個特定的上下文範圍內始終有效。然而,對不一樣類型的應用程序而言,要爲何是組成這種「上下文」下一個定義一般是困難的;不一樣的上下文對「當前」這個概念定義了不一樣的範圍。在3.0版本以前,使用Hibernate的程序要麼採用自行編寫的基於ThreadLocal的上下文session,要麼採用HibernateUtil這樣的輔助類,要麼採用第三方框架(好比Spring或Pico),它們提供了基於代理(proxy)或者基於攔截器(interception)的上下文相關session。
從3.0.1版本開始,Hibernate增長了SessionFactory.getCurrentSession()方法。一開始,它假定了採用JTA事務,JTA事務定義了當前session的範圍和上下文(scope and context)。Hibernate開發團隊堅信,由於有好幾個獨立的JTA TransactionManager實現穩定可用,不管是否被部署到一個J2EE容器中,大多數(倘若不是全部的)應用程序都應該採用JTA事務管理。基於這一點,採用JTA的上下文相關session能夠知足你一切須要。
更好的是,從3.1開始,SessionFactory.getCurrentSession()的後臺實現是可拔插的。所以,咱們引入了新的擴展接口(org.hibernate.context.CurrentSessionContext)和新的配置參數(hibernate.current_session_context_class),以便對什麼是「當前session」的範圍和上下文(scope and context)的定義進行拔插。
請參閱org.hibernate.context.CurrentSessionContext接口的Javadoc,那裏有關於它的契約的詳細討論。它定義了單一的方法,currentSession(),特定的實現用它來負責跟蹤當前的上下文session。Hibernate內置了此接口的兩種實現。
這兩種實現都提供了「每數據庫事務對應一個session」的編程模型,也稱做每次請求一個session。Hibernate session的起始和終結由數據庫事務的生存來控制。倘若你採用自行編寫代碼來管理事務(好比,在純粹的J2SE,或者JTA/UserTransaction/BMT),建議你使用Hibernate Transaction API來把底層事務實現從你的代碼中隱藏掉。若是你在支持CMT的EJB容器中執行,事務邊界是聲明式定義的,你不須要在代碼中進行任何事務或session管理操做。請參閱第 11 章 事務和併發一節來閱讀更多的內容和示例代碼。
hibernate.current_session_context_class配置參數定義了應該採用哪一個org.hibernate.context.CurrentSessionContext實現。注意,爲了向下兼容,若是未配置此參數,可是存在org.hibernate.transaction.TransactionManagerLookup的配置,Hibernate會採用org.hibernate.context.JTASessionContext。通常而言,此參數的值指明瞭要使用的實現類的全名,但那兩個內置的實現能夠使用簡寫,即"jta"和"thread"。
因爲Hibernate是爲了能在各類不一樣環境下工做而設計的, 所以存在着大量的配置參數. 幸運的是多數配置參數都有比較直觀的默認值, 並有隨Hibernate一同分發的配置樣例hibernate.properties (位於etc/)來展現各類配置選項. 所需作的僅僅是將這個樣例文件複製到類路徑 (classpath)下並作一些自定義的修改.
一個org.hibernate.cfg.Configuration實例表明了一個應用程序中Java類型到SQL數據庫映射的完整集合.Configuration被用來構建一個(不可變的(immutable))SessionFactory. 映射定義則由不一樣的XML映射定義文件編譯而來.
你能夠直接實例化Configuration來獲取一個實例,併爲它指定XML映射定義文件. 若是映射定義文件在類路徑(classpath)中, 請使用addResource():
Configuration cfg = new Configuration()
.addResource("Item.hbm.xml")
.addResource("Bid.hbm.xml");
一個替代方法(有時是更好的選擇)是,指定被映射的類,讓Hibernate幫你尋找映射定義文件:
Configuration cfg = new Configuration()
.addClass(org.hibernate.auction.Item.class)
.addClass(org.hibernate.auction.Bid.class);
Hibernate將會在類路徑(classpath)中尋找名字爲 /org/hibernate/auction/Item.hbm.xml和/org/hibernate/auction/Bid.hbm.xml映射定義文件. 這種方式消除了任何對文件名的硬編碼(hardcoded).
Configuration也容許你指定配置屬性:
Configuration cfg = new Configuration()
.addClass(org.hibernate.auction.Item.class)
.addClass(org.hibernate.auction.Bid.class)
.setProperty("hibernate.dialect","org.hibernate.dialect.MySQLInnoDBDialect")
.setProperty("hibernate.connection.datasource","java:comp/env/jdbc/test")
.setProperty("hibernate.order_updates", "true");
固然這不是惟一的傳遞Hibernate配置屬性的方式, 其餘可選方式還包括:
若是想盡快體驗Hibernate, hibernate.properties是最簡單的方式.
Configuration實例被設計成啓動期間(startup-time)對象, 一旦SessionFactory建立完成它就被丟棄了.
當全部映射定義被Configuration解析後, 應用程序必須得到一個用於構造Session實例的工廠. 這個工廠將被應用程序的全部線程共享:
SessionFactory sessions = cfg.buildSessionFactory();
Hibernate容許你的應用程序建立多個SessionFactory實例. 這對使用多個數據庫的應用來講頗有用.
一般你但願SessionFactory來爲你建立和緩存(pool)JDBC鏈接. 若是你採用這種方式, 只須要以下例所示那樣,打開一個Session:
Session session = sessions.openSession(); // open a new Session
一旦你須要進行數據訪問時, 就會從鏈接池(connectionpool)得到一個JDBC鏈接.
爲了使這種方式工做起來, 咱們須要向Hibernate傳遞一些JDBC鏈接的屬性. 全部Hibernate屬性的名字和語義都在org.hibernate.cfg.Environment中定義. 咱們如今將描述JDBC鏈接配置中最重要的設置.
若是你設置以下屬性,Hibernate將使用java.sql.DriverManager來得到(和緩存)JDBC鏈接 :
屬性名 |
用途 |
hibernate.connection.driver_class |
jdbc驅動類 |
hibernate.connection.url |
jdbc URL |
hibernate.connection.username |
數據庫用戶 |
hibernate.connection.password |
數據庫用戶密碼 |
hibernate.connection.pool_size |
鏈接池容量上限數目 |
但Hibernate自帶的鏈接池算法至關不成熟.它只是爲了讓你快些上手,並不適合用於產品系統或性能測試中。出於最佳性能和穩定性考慮你應該使用第三方的鏈接池。只須要用特定鏈接池的設置替換hibernate.connection.pool_size便可。這將關閉Hibernate自帶的鏈接池. 例如, 你可能會想用C3P0.
C3P0是一個隨Hibernate一同分發的開源的JDBC鏈接池, 它位於lib目錄下。若是你設置了hibernate.c3p0.*相關的屬性, Hibernate將使用 C3P0ConnectionProvider來緩存JDBC鏈接. 若是你更原意使用Proxool, 請參考發行包中的hibernate.properties併到Hibernate網站獲取更多的信息.
這是一個使用C3P0的hibernate.properties樣例文件:
hibernate.connection.driver_class= org.postgresql.Driver
hibernate.connection.url =jdbc:postgresql://localhost/mydatabase
hibernate.connection.username = myuser
hibernate.connection.password = secret
hibernate.c3p0.min_size=5
hibernate.c3p0.max_size=20
hibernate.c3p0.timeout=1800
hibernate.c3p0.max_statements=50
hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
爲了能在應用程序服務器(application server)中使用Hibernate, 應當老是將Hibernate配置成從註冊在JNDI中的Datasource處得到鏈接,你至少須要設置下列屬性中的一個:
屬性名 |
用途 |
hibernate.connection.datasource |
數據源JNDI名字 |
hibernate.jndi.url |
JNDI提供者的URL (可選) |
hibernate.jndi.class |
JNDI InitialContextFactory類 (可選) |
hibernate.connection.username |
數據庫用戶 (可選) |
hibernate.connection.password |
數據庫用戶密碼 (可選) |
這是一個使用應用程序服務器提供的JNDI數據源的hibernate.properties樣例文件:
hibernate.connection.datasource = java:/comp/env/jdbc/test
hibernate.transaction.factory_class = \
org.hibernate.transaction.JTATransactionFactory
hibernate.transaction.manager_lookup_class = \
org.hibernate.transaction.JBossTransactionManagerLookup
hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
從JNDI數據源得到的JDBC鏈接將自動參與到應用程序服務器中容器管理的事務(container-managed transactions)中去.
任何鏈接(connection)屬性的屬性名都要以"hibernate.connnection"開頭. 例如, 你可能會使用hibernate.connection.charSet來指定字符集charSet.
經過實現org.hibernate.connection.ConnectionProvider接口,你能夠定義屬於你本身的得到JDBC鏈接的插件策略。經過設置hibernate.connection.provider_class,你能夠選擇一個自定義的實現.
有大量屬性能用來控制Hibernate在運行期的行爲. 它們都是可選的, 並擁有適當的默認值.
警告: 其中一些屬性是"系統級(system-level)的". 系統級屬性只能經過java -Dproperty=value或hibernate.properties來設置, 而不能用上面描述的其餘方法來設置.
屬性名 |
用途 |
hibernate.dialect |
一個Hibernate Dialect類名容許Hibernate針對特定的關係數據庫生成優化的SQL. 取值 full.classname.of.Dialect |
hibernate.show_sql |
輸出全部SQL語句到控制檯. 有一個另外的選擇是把org.hibernate.SQL這個log category設爲debug。 eg. true | false |
hibernate.format_sql |
在log和console中打印出更漂亮的SQL。 取值 true | false |
hibernate.default_schema |
在生成的SQL中, 將給定的schema/tablespace附加於非全限定名的表名上. 取值 SCHEMA_NAME |
hibernate.default_catalog |
在生成的SQL中, 將給定的catalog附加於非全限定名的表名上. 取值 CATALOG_NAME |
hibernate.session_factory_name |
SessionFactory建立後,將自動使用這個名字綁定到JNDI中. 取值 jndi/composite/name |
hibernate.max_fetch_depth |
爲單向關聯(一對一, 多對一)的外鏈接抓取(outer join fetch)樹設置最大深度. 值爲0意味着將關閉默認的外鏈接抓取. 取值 建議在0到3之間取值 |
hibernate.default_batch_fetch_size |
爲Hibernate關聯的批量抓取設置默認數量. 取值 建議的取值爲4, 8, 和16 |
hibernate.default_entity_mode |
爲由這個SessionFactory打開的全部Session指定默認的實體表現模式. 取值 dynamic-map, dom4j, pojo |
hibernate.order_updates |
強制Hibernate按照被更新數據的主鍵,爲SQL更新排序。這麼作將減小在高併發系統中事務的死鎖。 取值 true | false |
hibernate.generate_statistics |
若是開啓, Hibernate將收集有助於性能調節的統計數據. 取值 true | false |
hibernate.use_identifer_rollback |
若是開啓, 在對象被刪除時生成的標識屬性將被重設爲默認值. 取值 true | false |
hibernate.use_sql_comments |
若是開啓, Hibernate將在SQL中生成有助於調試的註釋信息, 默認值爲false. 取值 true | false |
表 3.4. Hibernate JDBC和鏈接(connection)屬性
屬性名 |
用途 |
hibernate.jdbc.fetch_size |
非零值,指定JDBC抓取數量的大小 (調用Statement.setFetchSize()). |
hibernate.jdbc.batch_size |
非零值,容許Hibernate使用JDBC2的批量更新. 取值 建議取5到30之間的值 |
hibernate.jdbc.batch_versioned_data |
若是你想讓你的JDBC驅動從executeBatch()返回正確的行計數 , 那麼將此屬性設爲true(開啓這個選項一般是安全的). 同時,Hibernate將爲自動版本化的數據使用批量DML. 默認值爲false. eg. true | false |
hibernate.jdbc.factory_class |
選擇一個自定義的Batcher. 多數應用程序不須要這個配置屬性. eg. classname.of.Batcher |
hibernate.jdbc.use_scrollable_resultset |
容許Hibernate使用JDBC2的可滾動結果集. 只有在使用用戶提供的JDBC鏈接時,這個選項纔是必要的, 不然Hibernate會使用鏈接的元數據. 取值 true | false |
hibernate.jdbc.use_streams_for_binary |
在JDBC讀寫binary (二進制)或serializable (可序列化) 的類型時使用流(stream)(系統級屬性). 取值 true | false |
hibernate.jdbc.use_get_generated_keys |
在數據插入數據庫以後,容許使用JDBC3PreparedStatement.getGeneratedKeys() 來獲取數據庫生成的key(鍵)。須要JDBC3+驅動和JRE1.4+, 若是你的數據庫驅動在使用Hibernate的標 識生成器時遇到問題,請將此值設爲false. 默認狀況下將使用鏈接的元數據來斷定驅動的能力. 取值 true|false |
hibernate.connection.provider_class |
自定義ConnectionProvider的類名, 此類用來向Hibernate提供JDBC鏈接. 取值 classname.of.ConnectionProvider |
hibernate.connection.isolation |
設置JDBC事務隔離級別. 查看java.sql.Connection來了解各個值的具體意義, 但請注意多數數據庫都不支持全部的隔離級別. 取值 1, 2, 4, 8 |
hibernate.connection.autocommit |
容許被緩存的JDBC鏈接開啓自動提交(autocommit) (不建議). 取值 true | false |
hibernate.connection.release_mode |
指定Hibernate在什麼時候釋放JDBC鏈接. 默認狀況下,直到Session被顯式關閉或被斷開鏈接時,纔會釋放JDBC鏈接. 對於應用程序服務器的JTA數據源, 你應當使用after_statement, 這樣在每次JDBC調用後,都會主動的釋放鏈接. 對於非JTA的鏈接, 使用after_transaction在每一個事務結束時釋放鏈接是合理的. auto將爲JTA和CMT事務策略選擇after_statement, 爲JDBC事務策略選擇after_transaction. 取值 on_close | after_transaction | after_statement | auto |
hibernate.connection.<propertyName> |
將JDBC屬性propertyName傳遞到DriverManager.getConnection()中去. |
hibernate.jndi.<propertyName> |
將屬性propertyName傳遞到JNDI InitialContextFactory中去. |
屬性名 |
用途 |
hibernate.cache.provider_class |
自定義的CacheProvider的類名. 取值 classname.of.CacheProvider |
hibernate.cache.use_minimal_puts |
以頻繁的讀操做爲代價, 優化二級緩存來最小化寫操做. 在Hibernate3中,這個設置對的集羣緩存很是有用, 對集羣緩存的實現而言,默認是開啓的. 取值 true|false |
hibernate.cache.use_query_cache |
容許查詢緩存, 個別查詢仍然須要被設置爲可緩存的. 取值 true|false |
hibernate.cache.use_second_level_cache |
能用來徹底禁止使用二級緩存. 對那些在類的映射定義中指定<cache>的類,會默認開啓二級緩存. 取值 true|false |
hibernate.cache.query_cache_factory |
自定義實現QueryCache接口的類名, 默認爲內建的StandardQueryCache. 取值 classname.of.QueryCache |
hibernate.cache.region_prefix |
二級緩存區域名的前綴. 取值 prefix |
hibernate.cache.use_structured_entries |
強制Hibernate以更人性化的格式將數據存入二級緩存. 取值 true|false |
屬性名 |
用途 |
hibernate.transaction.factory_class |
一個TransactionFactory的類名, 用於Hibernate TransactionAPI (默認爲JDBCTransactionFactory). 取值 classname.of.TransactionFactory |
jta.UserTransaction |
一個JNDI名字,被JTATransactionFactory用來從應用服務器獲取JTA UserTransaction. 取值 jndi/composite/name |
hibernate.transaction.manager_lookup_class |
一個TransactionManagerLookup的類名 - 當使用JVM級緩存,或在JTA環境中使用hilo生成器的時候須要該類. 取值 classname.of.TransactionManagerLookup |
hibernate.transaction.flush_before_completion |
若是開啓, session在事務完成後將被自動清洗(flush)。 如今更好的方法是使用自動session上下文管理。請參見第 2.5 節 「上下文相關的(Contextual)Session」。 取值 true | false |
hibernate.transaction.auto_close_session |
若是開啓, session在事務完成後將被自動關閉。 如今更好的方法是使用自動session上下文管理。請參見第 2.5 節 「上下文相關的(Contextual)Session」。 取值 true | false |
屬性名 |
用途 |
hibernate.current_session_context_class |
爲"當前" Session指定一個(自定義的)策略。關於內置策略的詳情,請參見第 2.5 節 「上下文相關的(Contextual)Session」 。 eg. jta | thread | custom.Class |
hibernate.query.factory_class |
選擇HQL解析器的實現. 取值 org.hibernate.hql.ast.ASTQueryTranslatorFactory ororg.hibernate.hql.classic.ClassicQueryTranslatorFactory |
hibernate.query.substitutions |
將Hibernate查詢中的符號映射到SQL查詢中的符號 (符號多是函數名或常量名字). 取值 hqlLiteral=SQL_LITERAL, hqlFunction=SQLFUNC |
hibernate.hbm2ddl.auto |
在SessionFactory建立時,自動檢查數據庫結構,或者將數據庫schema的DDL導出到數據庫. 使用 create-drop時,在顯式關閉SessionFactory時,將drop掉數據庫schema. 取值 validate | update | create | create-drop |
hibernate.cglib.use_reflection_optimizer |
開啓CGLIB來替代運行時反射機制(系統級屬性). 反射機制有時在除錯時比較有用. 注意即便關閉這個優化, Hibernate仍是須要CGLIB. 你不能在hibernate.cfg.xml中設置此屬性. 取值 true | false |
你應當老是爲你的數據庫將hibernate.dialect屬性設置成正確的 org.hibernate.dialect.Dialect子類. 若是你指定一種方言,Hibernate將爲上面列出的一些屬性使用合理的默認值, 爲你省去了手工指定它們的功夫.
表 3.8. Hibernate SQL方言 (hibernate.dialect)
RDBMS |
方言 |
DB2 |
org.hibernate.dialect.DB2Dialect |
DB2 AS/400 |
org.hibernate.dialect.DB2400Dialect |
DB2 OS390 |
org.hibernate.dialect.DB2390Dialect |
PostgreSQL |
org.hibernate.dialect.PostgreSQLDialect |
MySQL |
org.hibernate.dialect.MySQLDialect |
MySQL with InnoDB |
org.hibernate.dialect.MySQLInnoDBDialect |
MySQL with MyISAM |
org.hibernate.dialect.MySQLMyISAMDialect |
Oracle (any version) |
org.hibernate.dialect.OracleDialect |
Oracle 9i/10g |
org.hibernate.dialect.Oracle9Dialect |
Sybase |
org.hibernate.dialect.SybaseDialect |
Sybase Anywhere |
org.hibernate.dialect.SybaseAnywhereDialect |
Microsoft SQL Server |
org.hibernate.dialect.SQLServerDialect |
SAP DB |
org.hibernate.dialect.SAPDBDialect |
Informix |
org.hibernate.dialect.InformixDialect |
HypersonicSQL |
org.hibernate.dialect.HSQLDialect |
Ingres |
org.hibernate.dialect.IngresDialect |
Progress |
org.hibernate.dialect.ProgressDialect |
Mckoi SQL |
org.hibernate.dialect.MckoiDialect |
Interbase |
org.hibernate.dialect.InterbaseDialect |
Pointbase |
org.hibernate.dialect.PointbaseDialect |
FrontBase |
org.hibernate.dialect.FrontbaseDialect |
Firebird |
org.hibernate.dialect.FirebirdDialect |
3.4.2. 外鏈接抓取(Outer Join Fetching)
若是你的數據庫支持ANSI, Oracle或Sybase風格的外鏈接, 外鏈接抓取一般能經過限制往返數據庫次數 (更多的工做交由數據庫本身來完成)來提升效率. 外鏈接抓取容許在單個SELECTSQL語句中,經過many-to-one, one-to-many, many-to-many和one-to-one關聯獲取鏈接對象的整個對象圖.
將hibernate.max_fetch_depth設爲0能在全局 範圍內禁止外鏈接抓取. 設爲1或更高值能啓用one-to-one和many-to-oneouter關聯的外鏈接抓取, 它們經過 fetch="join"來映射.
參見第 19.1 節「抓取策略(Fetching strategies) 」得到更多信息.
Oracle限制那些經過JDBC驅動傳輸的字節數組的數目. 若是你但願使用二進值 (binary)或 可序列化的 (serializable)類型的大對象, 你應該開啓 hibernate.jdbc.use_streams_for_binary屬性. 這是系統級屬性.
以hibernate.cache爲前綴的屬性容許你在Hibernate中,使用進程或羣集範圍內的二級緩存系統. 參見第 19.2 節「二級緩存(The Second Level Cache)」獲取更多的詳情.
你能夠使用hibernate.query.substitutions在Hibernate中定義新的查詢符號. 例如:
hibernate.query.substitutions true=1, false=0
將致使符號true和false在生成的SQL中被翻譯成整數常量.
hibernate.query.substitutions toLowercase=LOWER
將容許你重命名SQL中的LOWER函數.
3.4.6. Hibernate的統計(statistics)機制
若是你開啓hibernate.generate_statistics, 那麼當你經過 SessionFactory.getStatistics()調整正在運行的系統時,Hibernate將導出大量有用的數據.Hibernate甚至能被配置成經過JMX導出這些統計信息. 參考org.hibernate.stats中接口的Javadoc,以得到更多信息.
Hibernate使用Apache commons-logging來爲各類事件記錄日誌.
commons-logging將直接輸出到Apache Log4j(若是在類路徑中包括log4j.jar)或JDK1.4 logging (若是運行在JDK1.4或以上的環境下). 你能夠從http://jakarta.apache.org 下載Log4j. 要使用Log4j,你須要將log4j.properties文件放置在類路徑下, 隨Hibernate一同分發的樣例屬性文件在src/目錄下.
咱們強烈建議你熟悉一下Hibernate的日誌消息. 在不失可讀性的前提下,咱們作了不少工做,使Hibernate的日誌可能地詳細. 這是必要的查錯利器. 最使人感興趣的日誌分類有以下這些:
類別 |
功能 |
org.hibernate.SQL |
在全部SQL DML語句被執行時爲它們記錄日誌 |
org.hibernate.type |
爲全部JDBC參數記錄日誌 |
org.hibernate.tool.hbm2ddl |
在全部SQL DDL語句執行時爲它們記錄日誌 |
org.hibernate.pretty |
在session清洗(flush)時,爲全部與其關聯的實體(最多20個)的狀態記錄日誌 |
org.hibernate.cache |
爲全部二級緩存的活動記錄日誌 |
org.hibernate.transaction |
爲事務相關的活動記錄日誌 |
org.hibernate.jdbc |
爲全部JDBC資源的獲取記錄日誌 |
org.hibernate.hql.AST |
在解析查詢的時候,記錄HQL和SQL的AST分析日誌 |
org.hibernate.secure |
爲JAAS認證請求作日誌 |
org.hibernate |
爲任何Hibernate相關信息作日誌 (信息量較大, 但對查錯很是有幫助) |
在使用Hibernate開發應用程序時, 你應當老是爲org.hibernate.SQL 開啓debug級別的日誌記錄,或者開啓hibernate.show_sql屬性。
org.hibernate.cfg.NamingStrategy接口容許你爲數據庫中的對象和schema 元素指定一個「命名標準」.
你可能會提供一些經過Java標識生成數據庫標識或將映射定義文件中"邏輯"表/列名處理成"物理"表/列名的規則. 這個特性有助於減小冗長的映射定義文件.
在加入映射定義前,你能夠調用 Configuration.setNamingStrategy()指定一個不一樣的命名策略:
SessionFactory sf = new Configuration()
.setNamingStrategy(ImprovedNamingStrategy.INSTANCE)
.addFile("Item.hbm.xml")
.addFile("Bid.hbm.xml")
.buildSessionFactory();
org.hibernate.cfg.ImprovedNamingStrategy是一個內建的命名策略, 對一些應用程序而言,多是很是有用的起點.
另外一個配置方法是在hibernate.cfg.xml文件中指定一套完整的配置. 這個文件能夠當成hibernate.properties的替代。若兩個文件同時存在,它將覆蓋前者的屬性.
XML配置文件被默認是放在CLASSPATH的根目錄下. 這是一個例子:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!-- 以/jndi/name綁定到JNDI的SessionFactory實例 -->
<session-factory
name="java:hibernate/SessionFactory">
<!-- 屬性 -->
<property name="connection.datasource">java:/comp/env/jdbc/MyDB</property>
<propertyname="dialect">org.hibernate.dialect.MySQLDialect</property>
<propertyname="show_sql">false</property>
<propertyname="transaction.factory_class">
org.hibernate.transaction.JTATransactionFactory
</property>
<propertyname="jta.UserTransaction">java:comp/UserTransaction</property>
<!-- 映射定義文件 -->
<mappingresource="org/hibernate/auction/Item.hbm.xml"/>
<mappingresource="org/hibernate/auction/Bid.hbm.xml"/>
<!-- 緩存設置 -->
<class-cacheclass="org.hibernate.auction.Item" usage="read-write"/>
<class-cacheclass="org.hibernate.auction.Bid" usage="read-only"/>
<collection-cache collection="org.hibernate.auction.Item.bids"usage="read-write"/>
</session-factory>
</hibernate-configuration>
如你所見, 這個方法優點在於,在配置文件中指出了映射定義文件的名字. 一旦你須要調整Hibernate的緩存,hibernate.cfg.xml也是更方便. 注意,使用hibernate.properties仍是 hibernate.cfg.xml徹底是由你來決定, 除了上面提到的XML語法的優點以外, 二者是等價的.
使用XML配置,使得啓動Hibernate變的異常簡單, 以下所示,一行代碼就能夠搞定:
SessionFactory sf = newConfiguration().configure().buildSessionFactory();
你能夠使用以下代碼來添加一個不一樣的XML配置文件
SessionFactory sf = new Configuration()
.configure("catdb.cfg.xml")
.buildSessionFactory();
針對J2EE體系,Hibernate有以下幾個集成的方面:
若是應用程序服務器拋出"connection containment"異常, 根據你的環境,也許該將配置屬性hibernate.connection.release_mode設爲after_statement.
在你的架構中,Hibernate的Session API是獨立於任何事務分界系統的.若是你讓Hibernate經過鏈接池直接使用JDBC,你須要調用JDBC API來打開和關閉你的事務. 若是你運行在J2EE應用程序服務器中, 你也許想用Bean管理的事務並在須要的時候調用JTAAPI和UserTransaction.
爲了讓你的代碼在兩種(或其餘)環境中能夠移植,咱們建議使用可選的Hibernate Transaction API, 它包裝並隱藏了底層系統. 你必須經過設置Hibernate配置屬性hibernate.transaction.factory_class來指定一個Transaction實例的工廠類.
有三個標準(內建)的選擇:
org.hibernate.transaction.JDBCTransactionFactory
委託給數據庫(JDBC)事務(默認)
org.hibernate.transaction.JTATransactionFactory
若是在上下文環境中存在運行着的事務(如, EJB會話Bean的方法), 則委託給容器管理的事務, 不然,將啓動一個新的事務,並使用Bean管理的事務.
org.hibernate.transaction.CMTTransactionFactory
委託給容器管理的JTA事務
你也能夠定義屬於你本身的事務策略 (如, 針對CORBA的事務服務)
Hibernate的一些特性 (好比二級緩存,Contextual Sessions with JTA等等)須要訪問在託管環境中的JTATransactionManager. 因爲J2EE沒有標準化一個單一的機制,Hibernate在應用程序服務器中,你必須指定Hibernate如何得到TransactionManager的引用:
表 3.10. JTATransactionManagers
Transaction工廠類 |
應用程序服務器 |
org.hibernate.transaction.JBossTransactionManagerLookup |
JBoss |
org.hibernate.transaction.WeblogicTransactionManagerLookup |
Weblogic |
org.hibernate.transaction.WebSphereTransactionManagerLookup |
WebSphere |
org.hibernate.transaction.WebSphereExtendedJTATransactionLookup |
WebSphere 6 |
org.hibernate.transaction.OrionTransactionManagerLookup |
Orion |
org.hibernate.transaction.ResinTransactionManagerLookup |
Resin |
org.hibernate.transaction.JOTMTransactionManagerLookup |
JOTM |
org.hibernate.transaction.JOnASTransactionManagerLookup |
JOnAS |
org.hibernate.transaction.JRun4TransactionManagerLookup |
JRun4 |
org.hibernate.transaction.BESTransactionManagerLookup |
Borland ES |
與JNDI綁定的Hibernate的SessionFactory能簡化工廠的查詢,簡化建立新的Session. 須要注意的是這與JNDI綁定Datasource沒有關係, 它們只是恰巧用了相同的註冊表!
若是你但願將SessionFactory綁定到一個JNDI的名字空間, 用屬性hibernate.session_factory_name指定一個名字(如, java:hibernate/SessionFactory). 若是不設置這個屬性, SessionFactory將不會被綁定到JNDI中. (在以只讀JNDI爲默認實現的環境中,這個設置尤爲有用, 如Tomcat.)
在將SessionFactory綁定至JNDI時,Hibernate將使用hibernate.jndi.url, 和hibernate.jndi.class的值來實例化初始環境(initial context). 若是它們沒有被指定, 將使用默認的InitialContext.
在你調用cfg.buildSessionFactory()後, Hibernate會自動將SessionFactory註冊到JNDI. 這意味這你至少須要在你應用程序的啓動代碼(或工具類)中完成這個調用, 除非你使用HibernateService來作JMX部署 (見後面討論).
倘若你使用JNDI SessionFactory,EJB或者任何其它類均可以從JNDI中找到此SessionFactory。
咱們建議,在受管理的環境中,把SessionFactory綁定到JNDI,在其它狀況下,使用一個static(靜態的)singleton。爲了在你的應用程序代碼中隱藏這些細節,咱們還建議你用一個helper類把實際查找SessionFactory的代碼隱藏起來,好比HibernateUtil.getSessionFactory()。注意,這個類也就能夠方便地啓動Hibernate,參見第一章。
3.8.3. 在JTA環境下使用CurrentSession context (當前session上下文)管理
在Hibernate中,管理Session和transaction最好的方法是自動的"當前"Session管理。請參見第 2.5 節「上下文相關的(Contextual)Session」一節的討論。使用"jta"session上下文,倘若在當前JTA事務中尚未HibernateSession關聯,第一次sessionFactory.getCurrentSession()調用會啓動一個Session,並關聯到當前的JTA事務。在"jta"上下文中調用getCurrentSession()得到的Session,會被設置爲在transaction關閉的時候自動flush(清洗)、在transaction關閉以後自動關閉,每句語句以後主動釋放JDBC鏈接。這就能夠根據JTA事務的生命週期來管理與之關聯的Session,用戶代碼中就能夠再也不考慮這些管理。你的代碼也能夠經過UserTransaction用編程方式使用JTA,或者(咱們建議,爲了便於移植代碼)使用Hibernate的Transaction API來設置transaction邊界。若是你的代碼運行在EJB容器中,建議對CMT使用聲明式事務聲明。
爲了將SessionFactory註冊到JNDI中,cfg.buildSessionFactory()這行代碼仍需在某處被執行. 你可在一個static初始化塊(像HibernateUtil中的那樣)中執行它或將Hibernate部署爲一個託管的服務.
爲了部署在一個支持JMX的應用程序服務器上,Hibernate和 org.hibernate.jmx.HibernateService一同分發,如Jboss AS。實際的部署和配置是由應用程序服務器提供者指定的. 這裏是JBoss4.0.x的jboss-service.xml樣例:
<?xml version="1.0"?>
<server>
<mbean code="org.hibernate.jmx.HibernateService"
name="jboss.jca:service=HibernateFactory,name=HibernateFactory">
<!-- 必須的服務 -->
<depends>jboss.jca:service=RARDeployer</depends>
<depends>jboss.jca:service=LocalTxCM,name=HsqlDS</depends>
<!-- 將Hibernate服務綁定到JNDI -->
<attributename="JndiName">java:/hibernate/SessionFactory</attribute>
<!-- 數據源設置 -->
<attributename="Datasource">java:HsqlDS</attribute>
<attributename="Dialect">org.hibernate.dialect.HSQLDialect</attribute>
<!-- 事務集成 -->
<attributename="TransactionStrategy">
org.hibernate.transaction.JTATransactionFactory</attribute>
<attribute name="TransactionManagerLookupStrategy">
org.hibernate.transaction.JBossTransactionManagerLookup</attribute>
<attributename="FlushBeforeCompletionEnabled"&tt;true</attribute>
<attributename="AutoCloseSessionEnabled">true</attribute>
<!-- 抓取選項 -->
<attributename="MaximumFetchDepth">5</attribute>
<!-- 二級緩存 -->
<attributename="SecondLevelCacheEnabled">true</attribute>
<attributename="CacheProviderClass">org.hibernate.cache.EhCacheProvider</attribute>
<attribute name="QueryCacheEnabled">true</attribute>
<!-- 日誌 -->
<attributename="ShowSqlEnabled">true</attribute>
<!-- 映射定義文件 -->
<attributename="MapResources">auction/Item.hbm.xml,auction/Category.hbm.xml</attribute>
</mbean>
</server>
這個文件是部署在META-INF目錄下的, 並會被打包到以.sar (service archive)爲擴展名的JAR文件中. 同時,你須要將Hibernate、它所須要的第三方庫、你編譯好的持久化類以及你的映射定義文件打包進同一個文檔. 你的企業Bean(通常爲會話Bean)可能會被打包成它們本身的JAR文件, 但你也許會將EJBJAR文件一同包含進能獨立(熱)部署的主服務文檔. 參考JBossAS文檔以瞭解更多的JMX服務與EJB部署的信息.
在應用程序中,用來實現業務問題實體的(如,在電子商務應用程序中的Customer和Order)類就是持久化類。不能認爲全部的持久化類的實例都是持久的狀態——一個實例的狀態也可能是瞬時的或脫管的。
若是這些持久化類遵循一些簡單的規則,Hibernate可以工做得更好,這些規則也被稱做簡單傳統Java對象(POJO:PlainOld Java Object)編程模型。可是這些規則並非必需的。實際上,Hibernate3對於你的持久化類幾乎不作任何設想。你能夠用其餘的方法來表達領域模型:好比,使用Map實例的樹型結構。
大多數Java程序須要用一個持久化類來表示貓科動物。
package eg;
import java.util.Set;
import java.util.Date;
public class Cat {
private Long id; //identifier
private Datebirthdate;
private Color color;
private char sex;
private float weight;
private int litterId;
private Cat mother;
private Set kittens =new HashSet();
private voidsetId(Long id) {
this.id=id;
}
public Long getId() {
return id;
}
void setBirthdate(Datedate) {
birthdate = date;
}
public DategetBirthdate() {
return birthdate;
}
void setWeight(floatweight) {
this.weight =weight;
}
public floatgetWeight() {
return weight;
}
public ColorgetColor() {
return color;
}
void setColor(Colorcolor) {
this.color =color;
}
void setSex(char sex){
this.sex=sex;
}
public char getSex() {
return sex;
}
void setLitterId(intid) {
this.litterId =id;
}
public intgetLitterId() {
return litterId;
}
void setMother(Catmother) {
this.mother =mother;
}
public Cat getMother(){
return mother;
}
void setKittens(Setkittens) {
this.kittens =kittens;
}
public SetgetKittens() {
return kittens;
}
// addKitten notneeded by Hibernate
public voidaddKitten(Cat kitten) {
kitten.setMother(this);
kitten.setLitterId(kittens.size() );
kittens.add(kitten);
}
}
這裏要遵循四條主要的規則:
4.1.1. 實現一個默認的(即無參數的)構造方法(constructor)
Cat有一個無參數的構造方法。全部的持久化類都必須有一個默認的構造方法(能夠不是public的),這樣的話Hibernate就能夠使用 Constructor.newInstance()來實例化它們。咱們強烈建議,在Hibernate中,爲了運行期代理的生成,構造方法至少是 包(package)內可見的。
4.1.2. 提供一個標識屬性(identifier property)(可選)
Cat有一個屬性叫作id。這個屬性映射數據庫表的主鍵字段。這個屬性能夠叫任何名字,其類型能夠是任何的原始類型、原始類型的包裝類型、 java.lang.String 或者是 java.util.Date。(若是你的遺留數據庫表有聯合主鍵,你甚至能夠用一個用戶自定義的類,該類擁有這些類型的屬性。參見後面的關於聯合標識符的章節。)
標識符屬性是可選的。能夠不用管它,讓Hibernate內部來追蹤對象的識別。可是咱們並不推薦這樣作。
實際上,一些功能只對那些聲明瞭標識符屬性的類起做用:
咱們建議你對持久化類聲明命名一致的標識屬性。咱們還建議你使用一個能夠爲空(也就是說,不是原始類型)的類型。
代理(proxies)是Hibernate的一個重要的功能,它依賴的條件是,持久化類或者是非final的,或者是實現了一個全部方法都聲明爲public的接口。
你能夠用Hibernate持久化一個沒有實現任何接口的final類,可是你不能使用代理來延遲關聯加載,這會限制你進行性能優化的選擇。
你也應該避免在非final類中聲明 publicfinal的方法。若是你想使用一個有publicfinal方法的類,你必須經過設置lazy="false" 來明確地禁用代理。
4.1.4. 爲持久化字段聲明訪問器(accessors)和是否可變的標誌(mutators)(可選)
Cat爲它的全部持久化字段聲明瞭訪問方法。不少其餘ORM工具直接對實例變量進行持久化。咱們相信,在關係數據庫schema和類的內部數據結構之間引入間接層(原文爲"非直接",indirection)會好一些。默認狀況下Hibernate持久化JavaBeans風格的屬性,承認 getFoo,isFoo 和 setFoo這種形式的方法名。若是須要,你能夠對某些特定屬性實行直接字段訪問。
屬性不須要要聲明爲public的。Hibernate能夠持久化一個有 default、protected或private的get/set方法對的屬性進行持久化。
子類也必須遵照第一條和第二條規則。它從超類Cat繼承了標識屬性。
package eg;
public class DomesticCat extends Cat {
private Stringname;
public StringgetName() {
returnname;
}
protected voidsetName(String name) {
this.name=name;
}
}
若是你有以下需求,你必須重載 equals() 和 hashCode()方法:
Hibernate保證,僅在特定會話範圍內,持久化標識(數據庫的行)和Java標識是等價的。所以,一旦咱們混合了從不一樣會話中獲取的實例,若是但願Set有明確的語義,就必須實現equals() 和hashCode()。
實現equals()/hashCode()最顯而易見的方法是比較兩個對象標識符的值。若是值相同,則兩個對象對應於數據庫的同一行,所以它們是相等的(若是都被添加到 Set,則在Set中只有一個元素)。不幸的是,對生成的標識不能使用這種方法。Hibernate僅對那些持久化對象賦標識值,一個新建立的實例將不會有任何標識值。此外,若是一個實例沒有被保存(unsaved),而且它當前正在一個Set中,保存它將會給這個對象賦一個標識值。若是equals() 和hashCode()是基於標識值實現的,則其哈希碼將會改變,這違反了Set的契約。建議去Hibernate的站點閱讀關於這個問題的所有討論。注意,這不是Hibernate的問題,而是通常的Java對象標識和Java對象等價的語義問題。
咱們建議使用業務鍵值相等(Business key equality)來實現equals() 和 hashCode()。業務鍵值相等的意思是,equals()方法僅僅比較造成業務鍵的屬性,它能在現實世界裏標識咱們的實例(是一個天然的候選碼)。
public class Cat {
...
public booleanequals(Object other) {
if (this == other)return true;
if ( !(otherinstanceof Cat) ) return false;
final Cat cat =(Cat) other;
if (!cat.getLitterId().equals( getLitterId() ) ) return false;
if (!cat.getMother().equals( getMother() ) ) return false;
return true;
}
public int hashCode(){
int result;
result =getMother().hashCode();
result = 29 *result + getLitterId();
return result;
}
}
注意,業務鍵沒必要像數據庫的主鍵那樣固定不變(參見第 11.1.3 節「關注對象標識(Considering object identity)」)。對業務鍵而言,不可變或惟一的屬性是不錯的選擇。
注意,如下特性在當前處於試驗階段,未來可能會有變化。
運行期的持久化實體沒有必要必定表示爲像POJO類或JavaBean對象那樣的形式。Hibernate也支持動態模型(在運行期使用Map的Map)和象DOM4J的樹模型那樣的實體表示。使用這種方法,你不用寫持久化類,只寫映射文件就好了。
Hibernate默認工做在普通POJO模式。你能夠使用配置選項default_entity_mode,對特定的SessionFactory,設置一個默認的實體表示模式。(參見表 3.3 「Hibernate配置屬性」。)
下面是用Map來表示的例子。首先,在映射文件中,要聲明 entity-name來代替一個類名(或做爲一種附屬)。
<hibernate-mapping>
<classentity-name="Customer">
<idname="id"
type="long"
column="ID">
<generatorclass="sequence"/>
</id>
<propertyname="name"
column="NAME"
type="string"/>
<propertyname="address"
column="ADDRESS"
type="string"/>
<many-to-onename="organization"
column="ORGANIZATION_ID"
class="Organization"/>
<bagname="orders"
inverse="true"
lazy="false"
cascade="all">
<keycolumn="CUSTOMER_ID"/>
<one-to-many class="Order"/>
</bag>
</class>
</hibernate-mapping>
注意,雖然是用目標類名來聲明關聯的,可是關聯的目標類型除了是POJO以外,也能夠是一個動態的實體。
在使用dynamic-map爲SessionFactory 設置了默認的實體模式以後,能夠在運行期使用Map的 Map。
Session s = openSession();
Transaction tx = s.beginTransaction();
Session s = openSession();
// Create a customer
Map david = new HashMap();
david.put("name", "David");
// Create an organization
Map foobar = new HashMap();
foobar.put("name", "Foobar Inc.");
// Link both
david.put("organization", foobar);
// Save both
s.save("Customer", david);
s.save("Organization", foobar);
tx.commit();
s.close();
動態映射的好處是,變化所須要的時間少了,由於原型不須要實現實體類。然而,你沒法進行編譯期的類型檢查,並可能由此會處理不少的運行期異常。幸好有了Hibernate映射,它使得數據庫的schema能容易的規格化和合理化,並容許稍後在此之上添加合適的領域模型實現。
實體表示模式也能在每一個Session的基礎上設置:
Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
// Create a customer
Map david = new HashMap();
david.put("name", "David");
dynamicSession.save("Customer", david);
...
dynamicSession.flush();
dynamicSession.close()
...
// Continue on pojoSession
請注意,用EntityMode調用getSession()是在 Session的API中,而不是SessionFactory。這樣,新的Session共享底層的JDBC鏈接,事務,和其餘的上下文信息。這意味着,你不須要在第二個Session中調用 flush()和close(),一樣的,把事務和鏈接的處理交給原來的工做單元。
關於XML表示能力的更多信息能夠在第 18 章 XML映射中找到。
org.hibernate.tuple.Tuplizer,以及其子接口,負責根據給定的org.hibernate.EntityMode,來複現片段數據。若是給定的片段數據被認爲其是一種數據結構,"tuplizer"就是一個知道如何建立這樣的數據結構,以及如何給這個數據結構賦值的東西。好比說,對於POJO這種EntityMode,對應的tuplizer知道經過其構造方法來建立一個POJO,再經過其屬性訪問器來訪問POJO屬性。有兩大類高層Tuplizer,分別是org.hibernate.tuple.EntityTuplizer 和org.hibernate.tuple.ComponentTuplizer接口。EntityTuplizer負責管理上面提到的實體的契約,而ComponentTuplizer則是針對組件的。
用戶也能夠插入其自定義的tuplizer。或許您須要一種不一樣於dynamic-mapentity-mode中使用的java.util.HashMap的java.util.Map實現;或許您須要與默認策略不一樣的代理生成策略(proxy generation strategy)。經過自定義tuplizer實現,這兩個目標您均可以達到。Tuplizer定義被附加到它們指望管理的entity或者component映射中。回到咱們的customer entity例子:
<hibernate-mapping>
<classentity-name="Customer">
<!--
Override thedynamic-map entity-mode
tuplizer forthe customer entity
-->
<tuplizerentity-mode="dynamic-map"
class="CustomMapTuplizerImpl"/>
<idname="id" type="long" column="ID">
<generatorclass="sequence"/>
</id>
<!-- otherproperties -->
...
</class>
</hibernate-mapping>
public class CustomMapTuplizerImpl
extendsorg.hibernate.tuple.DynamicMapEntityTuplizer {
// override thebuildInstantiator() method to plug in our custom map...
protected finalInstantiator buildInstantiator(
org.hibernate.mapping.PersistentClass mappingInfo) {
return newCustomMapInstantiator( mappingInfo );
}
private static finalclass CustomMapInstantiator
extendsorg.hibernate.tuple.DynamicMapInstantitor {
// override thegenerateMap() method to return our custom map...
protected final Map generateMap() {
return new CustomMap();
}
}
}
TODO:property和proxy包裏的用戶擴展框架文檔。
第 5 章 對象/關係數據庫映射基礎(Basic O/R Mapping)
5.1. 映射定義(Mapping declaration)
對象和關係數據庫之間的映射一般是用一個XML文檔(XMLdocument)來定義的。這個映射文檔被設計爲易讀的,而且能夠手工修改。映射語言是以Java爲中心,這意味着映射文檔是按照持久化類的定義來建立的,而非表的定義。
請注意,雖然不少Hibernate用戶選擇手寫XML映射文檔,但也有一些工具能夠用來生成映射文檔,包括XDoclet,Middlegen和AndroMDA。
讓咱們從一個映射的例子開始:
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="eg">
<classname="Cat"
table="cats"
discriminator-value="C">
<idname="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"/>
<setname="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>
<classname="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)
node="element-name"
/>
(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 以前執行一次額外的SQLSELECT操做,來決定是否應該執行 UPDATE。 |
(11) |
polymorphism(多態) (可選, 默認值爲 implicit (隱式) ): 界定是隱式仍是顯式的使用多態查詢(這隻在Hibernate的具體表繼承策略中用到-譯註)。 |
(12) |
where (可選) 指定一個附加的SQLWHERE 條件, 在抓取這個類的對象時會一直增長這個條件。 |
(13) |
persister (可選): 指定一個定製的ClassPersister。 |
(14) |
batch-size (可選,默認是1) 指定一個用於 根據標識符(identifier)抓取實例時使用的"batch size"(批次抓取數量)。 |
(15) |
optimistic-lock(樂觀鎖定) (可選,默認是version): 決定樂觀鎖定的策略。 |
(16) |
lazy (可選): 經過設置lazy="false", 全部的延遲加載(Lazy fetching)功能將被所有禁用(disabled)。 |
(17) |
entity-name (可選,默認爲類名): Hibernate3容許一個類進行屢次映射( 前提是映射到不一樣的表),而且容許使用Maps或XML代替Java層次的實體映射 (也就是實現動態領域模型,不用寫持久化類-譯註)。 更多信息請看第 4.4 節 「動態模型(Dynamic models)」 and 第 18 章 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)中標識抽象超類。 |
若指明的持久化類其實是一個接口,這也是徹底能夠接受的。以後你能夠用元素<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 onbid.item_id = item.id
group by item.name
</subselect>
<synchronizetable="item"/>
<synchronizetable="bid"/>
<idname="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|.">
<generatorclass="generatorClass"/>
</id>
(1) |
name (可選): 標識屬性的名字。 |
(2) |
type (可選): 標識Hibernate類型的名字。 |
(3) |
column (可選 - 默認爲屬性名): 主鍵字段的名字。 |
(4) |
unsaved-value (可選 - 默認爲一個切合實際(sensible)的值): 一個特定的標識屬性值,用來標誌該實例是剛剛建立的,還沒有保存。 這能夠把這種實例和從之前的session中裝載過(可能又作過修改--譯者注) 但未再次持久化的實例區分開來。 |
(5) |
access (可選 - 默認爲property): Hibernate用來訪問屬性值的策略。 |
若是 name屬性不存在,會認爲這個類沒有標識屬性。
unsaved-value 屬性在Hibernate3中幾乎再也不須要。
還有一個另外的<composite-id>定義能夠訪問舊式的多主鍵數據。咱們強烈不建議使用這種方式。
可選的<generator>子元素是一個Java類的名字,用來爲該持久化類的實例生成惟一的標識。若是這個生成器實例須要某些配置值或者初始化參數,用<param>元素來傳遞。
<id name="id" type="long"column="cat_id">
<generatorclass="org.hibernate.id.TableHiLoGenerator">
<paramname="table">uid_table</param>
<paramname="column">next_hi_value_column</param>
</generator>
</id>
全部的生成器都實現org.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
在MSSQL Server 和 MySQL 中使用數據庫生成的GUID字符串。
native
根據底層數據庫的能力選擇identity, sequence 或者hilo中的一個。
assigned
讓應用程序在save()以前爲對象分配一個標示符。這是 <generator>元素沒有指定時的默認生成策略。
select
經過數據庫觸發器選擇一些惟一主鍵的行並返回主鍵值來分配一個主鍵。
foreign
使用另一個相關聯的對象的標識符。一般和<one-to-one>聯合起來使用。
5.1.4.2. 高/低位算法(Hi/LoAlgorithm)
hilo 和 seqhilo生成器給出了兩種hi/lo算法的實現,這是一種很使人滿意的標識符生成算法。第一種實現須要一個「特殊」的數據庫表來保存下一個可用的「hi」值。第二種實現使用一個Oracle風格的序列(在被支持的狀況下)。
<id name="id" type="long"column="cat_id">
<generatorclass="hilo">
<paramname="table">hi_value</param>
<paramname="column">next_value</param>
<paramname="max_lo">100</param>
</generator>
</id>
<id name="id" type="long"column="cat_id">
<generatorclass="seqhilo">
<paramname="sequence">hi_value</param>
<paramname="max_lo">100</param>
</generator>
</id>
很不幸,你在爲Hibernate自行提供Connection時沒法使用hilo。當Hibernate使用JTA獲取應用服務器的數據源鏈接時,你必須正確地配置 hibernate.transaction.manager_lookup_class。
5.1.4.3. UUID算法(UUID Algorithm )
UUID包含:IP地址,JVM的啓動時間(精確到1/4秒),系統時間和一個計數器值(在JVM中惟一)。在Java代碼中不可能得到MAC地址或者內存地址,因此這已是咱們在不使用JNI的前提下的能作的最好實現了。
5.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">
<generatorclass="sequence">
<paramname="sequence">person_id_sequence</param>
</generator>
</id>
<id name="id" type="long"column="person_id" unsaved-value="0">
<generatorclass="identity"/>
</id>
對於跨平臺開發,native策略會從identity, sequence 和hilo中進行選擇,選擇哪個,這取決於底層數據庫的支持能力。
5.1.4.5. 程序分配的標識符(Assigned Identifiers)
若是你須要應用程序分配一個標示符(而非Hibernate來生成),你能夠使用assigned 生成器。這種特殊的生成器會使用已經分配給對象的標識符屬性的標識符值。這個生成器使用一個天然鍵(naturalkey,有商業意義的列-譯註)做爲主鍵,而不是使用一個代理鍵( surrogate key,沒有商業意義的列-譯註)。這是沒有指定<generator>元素時的默認行爲
當選擇assigned生成器時,除非有一個version或timestamp屬性,或者你定義了 Interceptor.isUnsaved(),不然須要讓Hiberante使用 unsaved-value="undefined",強制Hibernatet查詢數據庫來肯定一個實例是瞬時的(transient)仍是脫管的(detached)。
5.1.4.6. 觸發器實現的主鍵生成器(Primary keys assigned by triggers)
僅僅用於遺留的schema中(Hibernate不能使用觸發器生成DDL)。
<id name="id" type="long"column="person_id">
<generatorclass="select">
<paramname="key">socialSecurityNumber</param>
</generator>
</id>
在上面的例子中,類定義了一個命名爲socialSecurityNumber的惟一值屬性,它是一個天然鍵(naturalkey),命名爲person_id的代理鍵(surrogate key)的值由觸發器生成。
<composite-id
name="propertyName"
class="ClassName"
mapped="true|false"
access="field|property|ClassName"
node="element-name|."
>
<key-propertyname="propertyName" type="typename"column="column_name"/>
<key-many-to-one name="propertyName class="ClassName"column="column_name"/>
......
</composite-id>
若是表使用聯合主鍵,你能夠映射類的多個屬性爲標識符屬性。 <composite-id>元素接受<key-property> 屬性映射和<key-many-to-one>屬性映射做爲子元素。
<composite-id>
<key-propertyname="medicareNumber"/>
<key-propertyname="dependent"/>
</composite-id>
你的持久化類必須重載equals()和 hashCode()方法,來實現組合的標識符的相等判斷。實現Serializable接口也是必須的。
不幸的是,這種組合關鍵字的方法意味着一個持久化類是它本身的標識。除了對象本身以外,沒有什麼方便的「把手」可用。你必須初始化持久化類的實例,填充它的標識符屬性,再load() 組合關鍵字關聯的持久狀態。咱們把這種方法稱爲embedded(嵌入式)的組合標識符,在重要的應用中不鼓勵使用這種用法。
第二種方法咱們稱爲mapped(映射式)組合標識符 (mapped composite identifier),<composite-id>元素中列出的標識屬性不但在持久化類出現,還造成一個獨立的標識符類。
<composite-id class="MedicareId"mapped="true">
<key-propertyname="medicareNumber"/>
<key-propertyname="dependent"/>
</composite-id>
在這個例子中,組合標識符類MedicareId和實體類都含有medicareNumber和dependent屬性。標識符類必須重載equals()和hashCode()而且實現Serializable接口。這種方法的缺點是出現了明顯的代碼重複。
下面列出的屬性是用來指定一個映射式組合標識符的:
在第 8.4 節「組件做爲聯合標識符(Components as composite identifiers)」一節中,咱們會描述第三種方式,那就是把組合標識符實現爲一個組件(component)類,這是更方便的方法。下面的屬性僅對第三種方法有效:
第三種方式,被稱爲identifier component(標識符組件)是咱們對幾乎全部應用都推薦使用的方式。
在"一棵對象繼承樹對應一個表"的策略中,<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表達式,在類型判斷(判斷是父類仍是具體子類-譯註)時執行。可用於基於內容的鑑別器。 |
鑑別器字段的實際值是根據<class>和<subclass>元素中的discriminator-value屬性得來的。
force屬性僅僅在這種狀況下有用的:表中包含沒有被映射到持久化類的附加辨別器值。這種狀況不會常常遇到。
使用formula屬性你能夠定義一個SQL表達式,用來判斷一個行數據的類型。
<discriminator
formula="casewhen 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)
generated="never|always" (6)
insert="true|false" (7)
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指明應被使用的標識屬性值。) |
(6) |
generated (可選 - 默認是 never): 代表此版本屬性值是否其實是由數據庫生成的。請參閱第 5.6 節 「數據庫生成屬性(Generated Properties)」部分的討論。 |
(7) |
insert (可選 - 默認是 true): 代表此版本列應該包含在SQL插入語句中。只有當數據庫字段有默認值0的時候,才能夠設置爲false。 |
版本號必須是如下類型:long, integer, short, timestamp或者calendar。
一個脫管(detached)實例的version或timestamp屬性不能爲空(null),由於Hibernate無論 unsaved-value被指定爲什麼種策略,它將任何屬性爲空的version或timestamp實例看做爲瞬時(transient)實例。 避免Hibernate中的傳遞重附(transitive reattachment)問題的一個簡單方法是定義一個不能爲空的version或timestamp屬性,特別是在人們使用程序分配的標識符(assigned identifiers)或複合主鍵時很是有用!
可選的<timestamp>元素指明瞭表中包含時間戳數據。這用來做爲版本的替代。時間戳本質上是一種對樂觀鎖定的一種不是特別安全的實現。固然,有時候應用程序可能在其餘方面使用時間戳。
<timestamp
column="timestamp_column" (1)
name="propertyName" (2)
access="field|property|ClassName" (3)
unsaved-value="null|undefined" (4)
source="vm|db" (5)
generated="never|always" (6)
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 指明使用標識屬性值進行這種判斷。) |
(5) |
source (可選 - 默認是 vm): Hibernate如何才能獲取到時間戳的值呢?從數據庫,仍是當前JVM?從數據庫獲取會帶來一些負擔,由於Hibernate必須訪問數據庫來得到「下一個值」,可是在集羣環境中會更安全些。還要注意,並非全部的Dialect(方言)都支持得到數據庫的當前時間戳的,而支持的數據庫中又有一部分由於精度不足,用於鎖定是不安全的(例如Oracle 8)。 |
(6) |
generated (可選 - 默認是 never): 指出時間戳值是否其實是由數據庫生成的.請參閱第 5.6 節 「數據庫生成屬性(Generated Properties)」的討論。 |
注意,<timestamp> 和<version type="timestamp">是等價的。而且<timestamp source="db">和<version type="dbtimestamp">是等價的。
<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)
generated="never|insert|always" (11)
node="element-name|@attribute-name|element/@attribute|."
index="index_name"
unique_key="unique_key_id"
length="L"
precision="P"
scale="S"
/>
(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): 指定 指定實例變量第一次被訪問時,這個屬性是否延遲抓取(fetched lazily)( 須要運行時字節碼加強)。 |
(8) |
unique (可選): 使用DDL爲該字段添加惟一的約束。 一樣,容許它做爲property-ref引用的目標。 |
(9) |
not-null (可選): 使用DDL爲該字段添加能否爲空(nullability)的約束。 |
(10) |
optimistic-lock (可選 - 默認爲 true): 指定這個屬性在作更新時是否須要得到樂觀鎖定(optimistic lock)。 換句話說,它決定這個屬性發生髒數據時版本(version)的值是否增加。 |
(11) |
generated (可選 - 默認爲 never): 代表此屬性值是否其實是由數據庫生成的。請參閱第 5.6 節 「數據庫生成屬性(Generated Properties)」的討論。 |
typename能夠是以下幾種:
若是你沒有指定類型,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="( SELECTSUM (li.quantity*p.price) FROM LineItem li, Product p
WHEREli.productId = p.productId
ANDli.customerId = customerId
ANDli.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="proxy|no-proxy|false" (12)
not-found="ignore|exception" (13)
entity-name="EntityName" (14)
formula="arbitrary SQL expression" (15)
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
index="index_name"
unique_key="unique_key_id"
foreign-key="foreign_key_name"
/>
(1) |
name: 屬性名。 |
(2) |
column (可選): 外間字段名。它也能夠經過嵌套的 <column>元素指定。 |
(3) |
class (可選 - 默認是經過反射獲得屬性類型): 關聯的類的名字。 |
(4) |
cascade(級聯) (可選): 指明哪些操做會從父對象級聯到關聯的對象。 |
(5) |
fetch (可選 - 默認爲 select): 在外鏈接抓取(outer-join fetching)和序列選擇抓取(sequential select fetching)二者中選擇其一。 |
(6) |
update, insert (可選 - 默認爲 true) 指定對應的字段是否包含在用於UPDATE 和/或 INSERT 的SQL語句中。若是兩者都是false,則這是一個純粹的 「外源性(derived)」關聯,它的值是經過映射到同一個(或多個)字段的某些其餘屬性獲得 或者經過trigger(觸發器)、或其餘程序生成。 |
(6) |
property-ref: (可選) 指定關聯類的一個屬性,這個屬性將會和本外鍵相對應。 若是沒有指定,會使用對方關聯類的主鍵。 |
(7) |
access (可選 - 默認是 property): Hibernate用來訪問屬性的策略。 |
(8) |
unique (可選): 使用DDL爲外鍵字段生成一個惟一約束。此外, 這也能夠用做property-ref的目標屬性。這使關聯同時具備 一對一的效果。 |
(9) |
not-null (可選): 使用DDL爲外鍵字段生成一個非空約束。 |
(10) |
optimistic-lock (可選 - 默認爲 true): 指定這個屬性在作更新時是否須要得到樂觀鎖定(optimistic lock)。 換句話說,它決定這個屬性發生髒數據時版本(version)的值是否增加。 |
(11) |
lazy (可選 - 默認爲 proxy): 默認狀況下,單點關聯是通過代理的。lazy="no-proxy"指定此屬性應該在實例變量第一次被訪問時應該延遲抓取(fetche lazily)(須要運行時字節碼的加強)。 lazy="false"指定此關聯老是被預先抓取。 |
(12) |
not-found (可選 - 默認爲 exception): 指定外鍵引用的數據不存在時如何處理: ignore會將行數據不存在視爲一個空(null)關聯。 |
(13) |
entity-name (可選): 被關聯的類的實體名。 |
(14) |
formula (可選): SQL表達式,用於定義computed(計算出的)外鍵值。 |
cascade屬性設置爲除了none之外任何有意義的值,它將把特定的操做傳遞到關聯對象中。這個值就表明着Hibernate基本操做的名稱, persist,merge, delete, save-update, evict, replicate, lock, refresh,以及特別的值delete-orphan和all,而且能夠用逗號分隔符來組合這些操做,例如,cascade="persist,merge,evict"或 cascade="all,delete-orphan"。更全面的解釋請參考第 10.11 節「傳播性持久化(transitive persistence)」. 注意,單值關聯(many-to-one 和 one-to-one 關聯) 不支持刪除孤兒(orphan delete,刪除再也不被引用的值).
一個典型的簡單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>的元素裏面映射全部關聯的屬性。
倘若被引用的惟一主鍵是組件的屬性,你能夠指定屬性路徑:
<many-to-one name="owner"property-ref="identity.ssn" column="OWNER_SSN"/>
持久化對象之間一對一的關聯關係是經過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="anySQL expression" (8)
lazy="proxy|no-proxy|false" (9)
entity-name="EntityName" (10)
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
foreign-key="foreign_key_name"
/>
(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="no-proxy"指定此屬性應該在實例變量第一次被訪問時應該延遲抓取(fetche 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">
<idname="id" column="PERSON_ID">
<generatorclass="foreign">
<paramname="property">employee</param>
</generator>
</id>
...
<one-to-onename="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"/>
<natural-id mutable="true|false"/>
<property .../>
<many-to-one... />
......
</natural-id>
咱們建議使用代用鍵(鍵值不&220855;備實際意義)做爲主鍵,咱們仍然應該嘗試爲全部的實體採用天然的鍵值做爲(附加——譯者注)標示。天然鍵(natural key)是單個或組合屬性,他們必須惟一且非空。若是它仍是不可變的那就更理想了。在<natural-id>元素中列出天然鍵的屬性。Hibernate會幫你生成必須的惟一鍵值和非空約束,你的映射會更加的明顯易懂(原文是self-documenting,自我註解)。
咱們強烈建議你實現equals() 和hashCode()方法,來比較實體的天然鍵屬性。
這一映射不是爲了把天然鍵做爲主鍵而準備的。
5.1.13. 組件(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的鍵值。參見第 8.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">
<idname="personNumber"/>
...
<propertiesname="name"
unique="true" update="false">
<propertyname="firstName"/>
<propertyname="initial"/>
<propertyname="lastName"/>
</properties>
</class>
而後,咱們可能有一些遺留的數據關聯,引用 Person表的這個惟一鍵,而不是主鍵。
<many-to-one name="person"
class="Person"property-ref="name">
<columnname="firstName"/>
<columnname="initial"/>
<columnname="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"
extends="SuperclassName">
<property ..../>
.....
</subclass>
(1) |
name: 子類的全限定名。 |
(2) |
discriminator-value(辨別標誌) (可選 - 默認爲類名):一個用於區分每一個獨立的子類的值。 |
(3) |
proxy(代理) (可選): 指定一個類或者接口,在延遲裝載時做爲代理使用。 |
(4) |
lazy (可選, 默認是true): 設置爲 lazy="false" 禁止使用延遲抓取 |
每一個子類都應該定義它本身的持久化屬性和子類。 <version> 和<id> 屬性能夠從根父類繼承下來。在一棵繼承樹上的每一個子類都必須定義一個惟一的discriminator-value。若是沒有指定,就會使用Java類的全限定名。
更多關於繼承映射的信息, 參考 第 9 章 繼承映射(Inheritance Mappings)章節.
5.1.16. 鏈接的子類(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">
<classname="Cat" table="CATS">
<idname="id" column="uid" type="long">
<generator class="hilo"/>
</id>
<property name="birthdate" type="date"/>
<propertyname="color" not-null="true"/>
<property name="sex" not-null="true"/>
<property name="weight"/>
<many-to-one name="mate"/>
<setname="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>
<classname="eg.Dog">
<!--mapping for Dog could go here -->
</class>
</hibernate-mapping>
更多關於繼承映射的信息,參考第 9 章 繼承映射(Inheritance Mappings)。
第三種選擇是僅僅映射類繼承樹中具體類部分到表中(每一個具體類一張表的策略)。其中,每張表定義了類的全部持久化狀態,包括繼承的狀態。在 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)字段。
更多關於繼承映射的信息,參考第 9 章 繼承映射(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">
<idname="id" column="PERSON_ID">...</id>
<jointable="ADDRESS">
<keycolumn="ADDRESS_ID"/>
<propertyname="address"/>
<propertyname="zip"/>
<propertyname="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 將使用數據庫級的ONCASCADE DELETE約束,而不是多個DELETE語句。注意,這個特性會繞過 Hibernate 一般對版本數據(versioneddata)採用的樂觀鎖策略。
not-null 和 update 屬性在映射單向一對多關聯的時候有用。若是你映射一個單向一對多關聯到非空的(non-nullable)外鍵,你必須 用<key not-null="true">定義此鍵字段。
5.1.20. 字段和規則元素(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="SQLexpression"
default="SQLexpression"/>
<formula>SQL expression</formula>
column 和 formula 屬性甚至能夠在同一個屬性或關聯映射中被合併來表達,例如,一些奇異的鏈接條件。
<many-to-one name="homeAddress"class="Address"
insert="false" update="false">
<columnname="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-valuevalue="TBL_ANIMAL" class="Animal"/>
<meta-valuevalue="TBL_HUMAN" class="Human"/>
<meta-valuevalue="TBL_ALIEN" class="Alien"/>
<columnname="table_name"/>
<columnname="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) |
爲了理解不少與持久化服務相關的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)語義。
內建的 基本映射類型能夠大體分爲
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
把字節數組(bytearrays)映射爲對應的 SQL二進制類型。
text
把長Java字符串映射爲SQL的CLOB或者TEXT類型。
serializable
把可序列化的Java類型映射到對應的SQL二進制類型。你也能夠爲一個並不是默認爲基本類型的可序列化Java類或者接口指定Hibernate類型serializable。
clob, blob
JDBC類 java.sql.Clob 和 java.sql.Blob的映射。某些程序可能不適合使用這個類型,由於blob和clob對象可能在一個事務以外是沒法重用的。(並且, 驅動程序對這種類型的支持充滿着補丁和先後矛盾。)
imm_date, imm_time, imm_timestamp,imm_calendar, imm_calendar_date, imm_serializable, imm_binary
通常來講,映射類型被假定爲是可變的Java類型,只有對不可變Java類型,Hibernate會採起特定的優化措施,應用程序會把這些對象做爲不可變對象處理。好比,你不該該對做爲imm_timestamp映射的Date執行Date.setTime()。要改變屬性的值,而且保存這一改變,應用程序必須對這一屬性從新設置一個新的(不同的)對象。
實體及其集合的惟一標識能夠是除了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">
<columnname="first_string"/>
<columnname="second_string"/>
</property>
注意使用<column>標籤來把一個屬性映射到多個字段的作法。
CompositeUserType, EnhancedUserType, UserCollectionType, 和 UserVersionType 接口爲更特殊的使用方式提供支持。
你甚至能夠在一個映射文件中提供參數給一個UserType。爲了這樣作,你的UserType必須實現org.hibernate.usertype.ParameterizedType接口。爲了給自定義類型提供參數,你能夠在映射文件中使用<type>元素。
<property name="priority">
<typename="com.mycompany.usertypes.DefaultValueIntegerType">
<paramname="default">0</param>
</type>
</property>
如今,UserType 能夠從傳入的Properties對象中獲得default 參數的值。
若是你很是頻繁地使用某一UserType,能夠爲他定義一個簡稱。這能夠經過使用 <typedef>元素來實現。Typedefs爲一自定義類型賦予一個名稱,而且若是此類型是參數化的,還能夠包含一系列默認的參數值。
<typedef class="com.mycompany.usertypes.DefaultValueIntegerType"name="default_zero">
<paramname="default">0</param>
</typedef>
<property name="priority"type="default_zero"/>
也能夠根據具體案例經過屬性映射中的類型參數覆蓋在typedef中提供的參數。
儘管 Hibernate 內建的豐富的類型和對組件的支持意味着你可能不多 須要使用自定義類型。不過,爲那些在你的應用中常常出現的(非實體)類使用自定義類型也是一個好方法。例如,一個MonetaryAmount類使用CompositeUserType來映射是不錯的選擇,雖然他能夠很容易地被映射成組件。這樣作的動機之一是抽象。使用自定義類型,之後倘若你改變表示金額的方法時,它能夠保證映射文件不須要修改。
對特定的持久化類,映射屢次是容許的。這種情形下,你必須指定entity name來區別不一樣映射實體的對象實例。(默認狀況下,實體名字和類名是相同的。) Hibernate在操做持久化對象、編寫查詢條件,或者把關聯映射到指定實體時,容許你指定這個entity name(實體名字)。
<class name="Contract" table="Contracts"
entity-name="CurrentContract">
...
<setname="history" inverse="true"
order-by="effectiveEndDate desc">
<keycolumn="currentContractId"/>
<one-to-manyentity-name="HistoricalContract"/>
</set>
</class>
<class name="Contract"table="ContractHistory"
entity-name="HistoricalContract">
...
<many-to-onename="currentContract"
column="currentContractId"
entity-name="CurrentContract"/>
</class>
注意這裏關聯是如何用entity-name來代替class的。
你可經過在映射文檔中使用反向引號(`)把表名或者字段名包圍起來,以強制Hibernate在生成的SQL中把標識符用引號包圍起來。Hibernate會使用相應的SQLDialect(方言)來使用正確的引號風格(一般是雙引號,可是在SQL Server中是括號,MySQL中是反向引號)。
<class name="LineItem" table="`LineItem`">
<idname="id" column="`Item Id`"/><generatorclass="assigned"/></id>
<propertyname="itemNumber" column="`Item #`"/>
...
</class>
XML 並不適用於全部人, 所以有其餘定義HibernateO/R 映射元數據(metadata)的方法。
不少Hibernate使用者更喜歡使用XDoclet@hibernate.tags將映射信息直接嵌入到源代碼中。咱們不會在本文檔中涉及這個方法,由於嚴格說來,這屬於XDoclet的一部分。然而,咱們包含了以下使用XDoclet映射的Cat類的例子。
package eg;
import java.util.Set;
import java.util.Date;
/**
* @hibernate.class
* table="CATS"
*/
public class Cat {
private Long id; //identifier
private Datebirthdate;
private Cat mother;
private Set kittens
private Color color;
private char sex;
private float weight;
/*
* @hibernate.id
* generator-class="native"
* column="CAT_ID"
*/
public Long getId() {
return id;
}
private voidsetId(Long id) {
this.id=id;
}
/**
*@hibernate.many-to-one
* column="PARENT_ID"
*/
public Cat getMother(){
return mother;
}
void setMother(Catmother) {
this.mother =mother;
}
/**
* @hibernate.property
* column="BIRTH_DATE"
*/
public Date getBirthdate(){
return birthdate;
}
void setBirthdate(Datedate) {
birthdate = date;
}
/**
* @hibernate.property
* column="WEIGHT"
*/
public floatgetWeight() {
return weight;
}
void setWeight(floatweight) {
this.weight =weight;
}
/**
* @hibernate.property
* column="COLOR"
* not-null="true"
*/
public ColorgetColor() {
return color;
}
void setColor(Colorcolor) {
this.color =color;
}
/**
* @hibernate.set
* inverse="true"
* order-by="BIRTH_DATE"
*@hibernate.collection-key
* column="PARENT_ID"
*@hibernate.collection-one-to-many
*/
public SetgetKittens() {
return kittens;
}
void setKittens(Setkittens) {
this.kittens =kittens;
}
// addKitten notneeded by Hibernate
public voidaddKitten(Cat kitten) {
kittens.add(kitten);
}
/**
* @hibernate.property
* column="SEX"
* not-null="true"
* update="false"
*/
public char getSex() {
return sex;
}
void setSex(char sex){
this.sex=sex;
}
}
參考Hibernate網站更多的Xdoclet和Hibernate的例子
5.5.2. 使用 JDK 5.0 的註解(Annotation)
JDK 5.0 在語言級別引入了 XDoclet 風格的標註,而且是類型安全的,在編譯期進行檢查。這一機制比XDoclet的註解更爲強大,有更好的工具和IDE支持。例如, IntelliJ IDEA,支持JDK 5.0註解的自動完成和語法高亮。EJB規範的新修訂版(JSR-220)使用 JDK 5.0的註解做爲entitybeans的主要元數據(metadata)機制。Hibernate3 實現了JSR-220 (the persistence API)的EntityManager,支持經過Hibernate Annotations包定義映射元數據。這個包做爲單獨的部分下載,支持EJB3 (JSR-220)和Hibernate3的元數據。
這是一個被註解爲EJB entity bean 的POJO類的例子
@Entity(access = AccessType.FIELD)
public class Customer implements Serializable {
@Id;
Long id;
String firstName;
String lastName;
Date birthday;
@Transient
Integer age;
@Embedded
private AddresshomeAddress;
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="CUSTOMER_ID")
Set<Order>orders;
// Getter/setter andbusiness methods
}
注意:對 JDK 5.0 註解 (和 JSR-220)支持的工做仍然在進行中,並未完成。更多細節請參閱Hibernate Annotations 模塊。
5.6. 數據庫生成屬性(Generated Properties)
Generated properties指的是其值由數據庫生成的屬性。通常來講,若是對象有任何屬性由數據庫生成值,Hibernate應用程序須要進行刷新(refresh)。但若是把屬性標明爲generated,就能夠轉由Hibernate來負責這個動做。實際上。對定義了generatedproperties的實體,每當Hibernate執行一條SQL INSERT或者UPDATE語句,會馬上執行一條select來得到生成的值。
被標明爲generated的屬性還必須是non-insertable和 non-updateable的。只有第 5.1.7 節「版本(version)(可選)」,第 5.1.8 節「timestamp (可選)」和第 5.1.9 節「property」能夠被標明爲generated。
never (默認) 標明此屬性值不是從數據庫中生成。
insert - 標明此屬性值在insert的時候生成,可是不會在隨後的update時從新生成。好比說建立日期就歸屬於這類。注意雖然第 5.1.7 節「版本(version)(可選)」和第 5.1.8 節「timestamp (可選)」屬性能夠被標註爲generated,可是不適用這個選項...
always - 標明此屬性值在insert和update時都會被生成。
5.7. 輔助數據庫對象(Auxiliary Database Objects)
Allows CREATE and DROP of arbitrarydatabase objects, in conjunction with Hibernate's schema evolution tools, toprovide the ability to fully define a user schema within the Hibernate mappingfiles. Although designed specifically for creating and dropping things liketriggers or stored procedures, really any SQL command that can be run via a java.sql.Statement.execute() method is valid here (ALTERs, INSERTS, etc). There areessentially two modes for defining auxiliary database objects... 幫助CREATE和DROP任意數據庫對象,與Hibernate的schema交互工具組合起來,能夠提供在Hibernate映射文件中徹底定義用戶schema的能力。雖然這是爲建立和銷燬trigger(觸發器)或storedprocedure(存儲過程)等特別設計的,實際上任何能夠在java.sql.Statement.execute()方法中執行的SQL命令均可以在此使用(好比ALTER,INSERT,等等)。本質上有兩種模式來定義輔助數據庫對象...
第一種模式是在映射文件中顯式聲明CREATE和DROP命令:
<hibernate-mapping>
...
<database-object>
<create>CREATE TRIGGER my_trigger ...</create>
<drop>DROPTRIGGER my_trigger</drop>
</database-object>
</hibernate-mapping>
第二種模式是提供一個類,這個類知道如何組織CREATE和DROP命令。這個特別類必須實現org.hibernate.mapping.AuxiliaryDatabaseObject接口。
<hibernate-mapping>
...
<database-object>
<definitionclass="MyTriggerDefinition"/>
</database-object>
</hibernate-mapping>
還有,這些數據庫對象能夠特別指定爲僅在特定的方言中才使用。
<hibernate-mapping>
...
<database-object>
<definitionclass="MyTriggerDefinition"/>
<dialect-scopename="org.hibernate.dialect.Oracle9Dialect"/>
<dialect-scopename="org.hibernate.dialect.OracleDialect"/>
</database-object>
</hibernate-mapping>
6.1. 持久化集合類(Persistent collections)
(譯者注:在閱讀本章的時候,之後整個手冊的閱讀過程當中,咱們都會面臨一個名詞方面的問題,那就是「集合」。"Collections"和"Set"在中文裏對應都被翻譯爲「集合」,可是他們的含義很不同。Collections是一個超集,Set是其中的一種。大部分狀況下,本譯稿中泛指的未加英文註明的「集合」,都應當理解爲「Collections」。在有些兩者同時出現,可能形成混淆的地方,咱們用「集合類」來特指「Collecions」,「集合(Set)」來指"Set",通常都會在後面的括號中給出英文。但願你們在閱讀時聯繫上下文理解,不要形成誤解。與此同時,「元素」一詞對應的英文「element」,也有兩個不一樣的含義。其一爲集合的元素,是內存中的一個變量;另外一含義則是XML文檔中的一個標籤所表明的元素。也請注意區別。本章中,特別是後半部分是須要反覆閱讀才能理解清楚的。若是遇到任何疑問,請記住,英文版本的reference是唯一標準的參考資料。)
Hibernate要求持久化集合值字段必須聲明爲接口,好比:
public class Product {
private StringserialNumber;
private Set parts =new HashSet();
public Set getParts(){ return parts; }
void setParts(Setparts) { this.parts = parts; }
public StringgetSerialNumber() { return serialNumber; }
voidsetSerialNumber(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 orArrayList。
集合類實例具備值類型的一般行爲。當被持久化對象引用後,他們會自動被持久化,當再也不被引用後,自動被刪除。倘若實例被從一個持久化對象傳遞到另外一個,它的元素可能從一個錶轉移到另外一個表。兩個實體不能共享同一個集合類實例的引用。由於底層關係數據庫模型的緣由,集合值屬性沒法支持空值語義;Hibernate對空的集合引用和空集合不加區別。
你不須要過多的爲此擔憂。就如同你平時使用普通的Java集合類同樣來使用持久化集合類。只是要確認你理解了雙向關聯的語義(後文討論)。
6.2. 集合映射( Collection mappings )
用於映射集合類的Hibernate映射元素取決於接口的類型。好比, <set> 元素用來映射Set類型的屬性。
<class name="Product">
<idname="serialNumber" column="productSerialNumber"/>
<setname="parts">
<keycolumn="productSerialNumber" not-null="true"/>
<one-to-manyclass="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|extra|false" (4)
inverse="true|false" (5)
cascade="all|none|save-update|delete|all-delete-orphan|delet(6)e-orphan"
sort="unsorted|natural|comparatorClass" (7)
order-by="column_name asc|desc" (8)
where="arbitrarysql where condition" (9)
fetch="join|select|subselect" (10)
batch-size="N" (11)
access="field|property|ClassName" (12)
optimistic-lock="true|false" (13)
mutable="true|false" (14)
node="element-name|."
embed-xml="true|false"
>
<key .... />
<map-key .... />
<element .... />
</map>
(1) |
name 集合屬性的名稱 |
(2) |
table (可選——默認爲屬性的名稱)這個集合表的名稱(不能在一對多的關聯關係中使用) |
(3) |
schema (可選) 表的schema的名稱, 他將覆蓋在根元素中定義的schema |
(4) |
lazy (可選--默認爲true) 能夠用來關閉延遲加載(false),指定一直使用預先抓取,或者打開"extra-lazy" 抓取,此時大多數操做不會初始化集合類(適用於很是大的集合) |
(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取得集合屬性值時使用的策略 |
(13) |
樂觀鎖 (可選 - 默認爲 true): 對集合的狀態的改變會是否致使其所屬的實體的版本增加。 (對一對多關聯來講,關閉這個屬性經常是有理的) |
(14) |
mutable(可變) (可選 - 默認爲true): 若值爲false,代表集合中的元素不會改變(在某些狀況下能夠進行一些小的性能優化)。 |
6.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> 元素的完整定義,請參閱前面的章節。
6.2.2. 集合元素(Collection elements)
集合幾乎能夠包含任何其餘的Hibernate類型,包括全部的基本類型、自定義類型、組件,固然還有對其餘實體的引用。存在一個重要的區別:位於集合中的對象多是根據「值」語義來操做(其聲明週期徹底依賴於集合持有者),或者它多是指向另外一個實體的引用,具備其本身的生命週期。在後者的狀況下,被做爲集合持有的狀態考慮的,只有兩個對象之間的「鏈接」。
被包容的類型被稱爲集合元素類型(collection element type)。集合元素經過<element>或<composite-element>映射,或在其是實體引用的時候,經過<one-to-many> 或<many-to-many>映射。前兩種用於使用值語義映射元素,後兩種用於映射實體關聯。
6.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="anySQL expression" (2)
type="type_name" (3)
node="@attribute-name"
length="N"/>
(1) |
column(可選):保存集合索引值的字段名。 |
(2) |
formula (可選): 用於計算map關鍵字的SQL公式 |
(3) |
type (必須):映射鍵(map key)的類型。 |
<map-key-many-to-many
column="column_name" (1)
formula="anySQL expression" (2)(3)
class="ClassName"
/>
(1) |
column(可選):集合索引值中外鍵字段的名稱 |
(2) |
formula (可選): 用於計算map關鍵字的外鍵的SQL公式 |
(3) |
class (必需):映射的鍵(map key)使用的實體類。 |
倘若你的表沒有一個索引字段,當你仍然但願使用List做爲屬性類型,你應該把此屬性映射爲Hibernate <bag>。從數據庫中獲取的時候,bag不維護其順序,但也可選擇性的進行排序。
從集合類能夠產生很大一部分映射,覆蓋了不少常見的關係模型。咱們建議你試驗schema生成工具,來體會一下不一樣的映射聲明是如何被翻譯爲數據庫表的。
6.2.4. 值集合於多對多關聯(Collections of values and many-to-many associations)
任何值集合或者多對多關聯須要專用的具備一個或多個外鍵字段的collection table、一個或多個collection element column,以及還可能有一個或多個索引字段。
對於一個值集合, 咱們使用<element>標籤。
<element
column="column_name" (1)
formula="anySQL expression" (2)
type="typename" (3)
length="L"
precision="P"
scale="S"
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="anySQL expression" (2)
class="ClassName" (3)
fetch="select|join" (4)
unique="true|false" (5)
not-found="ignore|exception" (6)
entity-name="EntityName" (7)
property-ref="propertyNameFromAssociatedClass" (8)
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的替代。 |
(8) |
property-ref: (可選) 被關聯到此外鍵(foreign key)的類中的對應屬性的名字。若未指定,使用被關聯類的主鍵。 |
例子:首先, 一組字符串:
<set name="names" table="NAMES">
<keycolumn="GROUPID"/>
<elementcolumn="NAME" type="string"/>
</set>
包含一組整數的bag(還設置了order-by參數指定了迭代的順序):
<bag name="sizes"
table="item_sizes"
order-by="size asc">
<keycolumn="item_id"/>
<elementcolumn="size" type="integer"/>
</bag>
一個實體數組,在這個案例中是一個多對多的關聯(注意這裏的實體是自動管理生命週期的對象(lifecycle objects),cascade="all"):
<array name="addresses"
table="PersonAddress"
cascade="persist">
<keycolumn="personId"/>
<list-index column="sortOrder"/>
<many-to-manycolumn="addressId" class="Address"/>
</array>
一個map,經過字符串的索引來指明日期:
<map name="holidays"
table="holidays"
schema="dbo"
order-by="hol_name asc">
<keycolumn="id"/>
<map-keycolumn="hol_name" type="string"/>
<elementcolumn="hol_date" type="date"/>
</map>
一個組件的列表:(下一章討論)
<list name="carComponents"
table="CarComponents">
<keycolumn="carId"/>
<list-indexcolumn="sortOrder"/>
<composite-elementclass="CarComponent">
<propertyname="price"/>
<propertyname="type"/>
<propertyname="serialNumber" column="serialNum"/>
</composite-element>
</list>
6.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">
<keycolumn="foo_id"/>
<one-to-manyclass="org.hibernate.Bar"/>
</set>
注意:<one-to-many>元素不須要定義任何字段。也不須要指定表名。
重要提示:若是一對多關聯中的外鍵字段定義成NOTNULL,你必須把<key>映射聲明爲not-null="true",或者使用雙向關聯,而且標明inverse="true"。參閱本章後面關於雙向關聯的討論。
下面的例子展現一個Part實體的map,把name做爲關鍵字。( partName 是Part的持久化屬性)。注意其中的基於公式的索引的用法。
<map name="parts"
cascade="all">
<keycolumn="productId" not-null="true"/>
<map-key formula="partName"/>
<one-to-manyclass="Part"/>
</map>
6.3. 高級集合映射(Advanced collection mappings)
6.3.1. 有序集合(Sorted collections)
Hibernate支持實現java.util.SortedMap和java.util.SortedSet的集合。你必須在映射文件中指定一個比較器:
<set name="aliases"
table="person_aliases"
sort="natural">
<keycolumn="person"/>
<elementcolumn="name" type="string"/>
</set>
<map name="holidays"sort="my.custom.HolidayComparator">
<keycolumn="year_id"/>
<map-keycolumn="hol_name" type="string"/>
<elementcolumn="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">
<keycolumn="person"/>
<elementcolumn="name" type="string"/>
</set>
<map name="holidays" order-by="hol_date,hol_name">
<keycolumn="year_id"/>
<map-keycolumn="hol_name" type="string"/>
<elementcolumn="hol_date" type="date"/>
</map>
注意: 這個order-by屬性的值是一個SQL排序子句而不是HQL的!
關聯還能夠在運行時使用集合filter()根據任意的條件來排序。
sortedUsers = s.createFilter( group.getUsers(), "order bythis.name" ).list();
6.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">
<idname="id" column="CATEGORY_ID"/>
...
<bagname="items" table="CATEGORY_ITEM">
<key column="CATEGORY_ID"/>
<many-to-manyclass="Item" column="ITEM_ID"/>
</bag>
</class>
<class name="Item">
<idname="id" column="CATEGORY_ID"/>
...
<!-- inverse end-->
<bagname="categories" table="CATEGORY_ITEM"inverse="true">
<keycolumn="ITEM_ID"/>
<many-to-manyclass="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" aboutthe relationship
session.persist(item); // The relationshipwon''t be saved!
session.persist(category); // The relationship will besaved
非反向端用於把內存中的表示保存到數據庫中。
要創建一個一對多的雙向關聯,你能夠經過把一個一對多關聯,做爲一個多對一關聯映射到到同一張表的字段上,而且在"多"的那一端定義inverse="true"。
<class name="Parent">
<idname="id" column="parent_id"/>
....
<setname="children" inverse="true">
<keycolumn="parent_id"/>
<one-to-manyclass="Child"/>
</set>
</class>
<class name="Child">
<idname="id" column="child_id"/>
....
<many-to-onename="parent"
class="Parent"
column="parent_id"
not-null="true"/>
</class>
在「一」這一端定義inverse="true"不會影響級聯操做,兩者是正交的概念!
對於有一端是<list>或者<map>的雙向關聯,須要加以特別考慮。倘若子類中的一個屬性映射到索引字段,沒問題,咱們仍然能夠在集合類映射上使用inverse="true":
<class name="Parent">
<idname="id" column="parent_id"/>
....
<mapname="children" inverse="true">
<keycolumn="parent_id"/>
<map-keycolumn="name"
type="string"/>
<one-to-manyclass="Child"/>
</map>
</class>
<class name="Child">
<idname="id" column="child_id"/>
....
<propertyname="name"
not-null="true"/>
<many-to-onename="parent"
class="Parent"
column="parent_id"
not-null="true"/>
</class>
可是,倘若子類中沒有這樣的屬性存在,咱們不能認爲這個關聯是真正的雙向關聯(信息不對稱,在關聯的一端有一些另一端沒有的信息)。在這種狀況下,咱們不能使用inverse="true"。咱們須要這樣用:
<class name="Parent">
<idname="id" column="parent_id"/>
....
<mapname="children">
<keycolumn="parent_id"
not-null="true"/>
<map-keycolumn="name"
type="string"/>
<one-to-manyclass="Child"/>
</map>
</class>
<class name="Child">
<idname="id" column="child_id"/>
....
<many-to-onename="parent"
class="Parent"
column="parent_id"
insert="false"
update="false"
not-null="true"/>
</class>
注意在這個映射中,關聯中集合類"值"一端負責來更新外鍵.TODO: Does this really result in some unnecessary updatestatements?
6.3.4. 三重關聯(Ternary associations)
有三種可能的途徑來映射一個三重關聯。第一種是使用一個Map,把一個關聯做爲其索引:
<map name="contracts">
<keycolumn="employer_id" not-null="true"/>
<map-key-many-to-many column="employee_id"class="Employee"/>
<one-to-manyclass="Contract"/>
</map>
<map name="connections">
<keycolumn="incoming_node_id"/>
<map-key-many-to-many column="outgoing_node_id"class="Node"/>
<many-to-manycolumn="connection_id" class="Connection"/>
</map>
第二種方法是簡單的把關聯從新建模爲一個實體類。這使咱們最常用的方法。
最後一種選擇是使用複合元素,咱們會在後面討論
若是你徹底信奉咱們對於「聯合主鍵(composite keys)是個壞東西」,和「實體應該使用(無機的)本身生成的代用標識符(surrogate keys)」的觀點,也許你會感到有一些奇怪,咱們目前爲止展現的多對多關聯和值集合都是映射成爲帶有聯合主鍵的表的!如今,這一點很是值得爭辯;看上去一個單純的關聯表並不能從代用標識符中得到什麼好處(雖然使用組合值的集合可能會得到一點好處)。不過,Hibernate提供了一個(一點點試驗性質的)功能,讓你把多對多關聯和值集合應獲得一個使用代用標識符的表去。
<idbag> 屬性讓你使用bag語義來映射一個List (或Collection)。
<idbag name="lovers" table="LOVERS">
<collection-idcolumn="ID" type="long">
<generatorclass="sequence"/>
</collection-id>
<keycolumn="PERSON1"/>
<many-to-manycolumn="PERSON2" class="Person" fetch="join"/>
</idbag>
你能夠理解,<idbag>人工的id生成器,就好像是實體類同樣!集合的每一行都有一個不一樣的人造關鍵字。可是,Hibernate沒有提供任何機制來讓你取得某個特定行的人造關鍵字。
注意<idbag>的更新性能要比普通的<bag>高得多!Hibernate能夠有效的定位到不一樣的行,分別進行更新或刪除工做,就如同處理一個list, map或者set同樣。
在目前的實現中,還不支持使用identity標識符生成器策略來生成<idbag>集合的標識符。
在前面的幾個章節的確很是使人迷惑。所以讓咱們來看一個例子。這個類:
package eg;
import java.util.Set;
public class Parent {
private long id;
private Set children;
public long getId() {return id; }
private voidsetId(long id) { this.id=id; }
private SetgetChildren() { return children; }
private voidsetChildren(Set children) { this.children=children; }
....
....
}
這個類有一個Child的實例集合。若是每個子實例至多有一個父實例, 那麼最天然的映射是一個one-to-many的關聯關係:
<hibernate-mapping>
<classname="Parent">
<idname="id">
<generatorclass="sequence"/>
</id>
<setname="children">
<keycolumn="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<classname="Child">
<idname="id">
<generatorclass="sequence"/>
</id>
<propertyname="name"/>
</class>
</hibernate-mapping>
在如下的表定義中反應了這個映射關係:
create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, namevarchar(255), parent_id bigint )
alter table child add constraint childfk0 (parent_id) referencesparent
若是父親是必須的, 那麼就能夠使用雙向one-to-many的關聯了:
<hibernate-mapping>
<classname="Parent">
<idname="id">
<generatorclass="sequence"/>
</id>
<setname="children" inverse="true">
<keycolumn="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<classname="Child">
<idname="id">
<generatorclass="sequence"/>
</id>
<propertyname="name"/>
<many-to-onename="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,
namevarchar(255),
parent_id bigint not null )
alter table child add constraint childfk0 (parent_id) referencesparent
另外,若是你絕對堅持這個關聯應該是單向的,你能夠對<key>映射聲明NOT NULL約束:
<hibernate-mapping>
<classname="Parent">
<idname="id">
<generatorclass="sequence"/>
</id>
<setname="children">
<keycolumn="parent_id" not-null="true"/>
<one-to-many class="Child"/>
</set>
</class>
<classname="Child">
<idname="id">
<generatorclass="sequence"/>
</id>
<propertyname="name"/>
</class>
</hibernate-mapping>
另一方面,若是一個子實例可能有多個父實例,那麼就應該使用many-to-many關聯:
<hibernate-mapping>
<classname="Parent">
<idname="id">
<generatorclass="sequence"/>
</id>
<setname="children" table="childset">
<key column="parent_id"/>
<many-to-many class="Child"column="child_id"/>
</set>
</class>
<classname="Child">
<idname="id">
<generatorclass="sequence"/>
</id>
<propertyname="name"/>
</class>
</hibernate-mapping>
表定義:
create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, namevarchar(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
更多的例子,以及一個完整的父/子關係映射的排練,請參閱第 21 章 示例:父子關係(Parent Child Relationships).
甚至可能出現更加複雜的關聯映射,咱們會在下一章中列出全部可能性。
關聯關係映射一般狀況是最難配置正確的。在這個部分中,咱們從單向關係映射開始,而後考慮雙向關係映射,由淺至深講述一遍典型的案例。在全部的例子中,咱們都使用 Person和Address。
咱們根據映射關係是否涉及鏈接表以及多樣性來劃分關聯類型。
在傳統的數據建模中,容許爲Null值的外鍵被認爲是一種很差的實踐,所以咱們全部的例子中都使用不容許爲Null的外鍵。這並非Hibernate的要求,即便你刪除掉不容許爲Null的約束,Hibernate映射同樣能夠工做的很好。
7.2. 單向關聯(Unidirectional associations)
單向many-to-one關聯是最多見的單向關聯關係。
<class name="Person">
<idname="id" column="personId">
<generatorclass="native"/>
</id>
<many-to-onename="address"
column="addressId"
not-null="true"/>
</class>
<class name="Address">
<idname="id" column="addressId">
<generatorclass="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key,addressId bigint not null )
create table Address ( addressId bigint not null primary key )
基於外鍵關聯的單向一對一關聯和單向多對一關聯幾乎是同樣的。惟一的不一樣就是單向一對一關聯中的外鍵字段具備惟一性約束。
<class name="Person">
<idname="id" column="personId">
<generatorclass="native"/>
</id>
<many-to-onename="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<class name="Address">
<idname="id" column="addressId">
<generatorclass="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key,addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
基於主鍵關聯的單向一對一關聯一般使用一個特定的id生成器。(請注意,在這個例子中咱們掉換了關聯的方向。)
<class name="Person">
<idname="id" column="personId">
<generatorclass="native"/>
</id>
</class>
<class name="Address">
<idname="id" column="personId">
<generatorclass="foreign">
<paramname="property">person</param>
</generator>
</id>
<one-to-onename="person" constrained="true"/>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )
基於外鍵關聯的單向一對多關聯是一種不多見的狀況,並不推薦使用。
<class name="Person">
<idname="id" column="personId">
<generatorclass="native"/>
</id>
<setname="addresses">
<keycolumn="personId"
not-null="true"/>
<one-to-manyclass="Address"/>
</set>
</class>
<class name="Address">
<idname="id" column="addressId">
<generatorclass="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( addressId bigint not null primary key,personId bigint not null )
咱們認爲對於這種關聯關係最好使用鏈接表。
7.3. 使用鏈接表的單向關聯(Unidirectional associations with join tables)
基於鏈接表的單向一對多關聯 應該優先被採用。請注意,經過指定unique="true",咱們能夠把多樣性從多對多改變爲一對多。
<class name="Person">
<idname="id" column="personId">
<generatorclass="native"/>
</id>
<setname="addresses" table="PersonAddress">
<keycolumn="personId"/>
<many-to-manycolumn="addressId"
unique="true"
class="Address"/>
</set>
</class>
<class name="Address">
<idname="id" column="addressId">
<generatorclass="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId not null, addressId bigintnot null primary key )
create table Address ( addressId bigint not null primary key )
基於鏈接表的單向多對一關聯在關聯關係可選的狀況下應用也很廣泛。
<class name="Person">
<idname="id" column="personId">
<generatorclass="native"/>
</id>
<jointable="PersonAddress"
optional="true">
<keycolumn="personId" unique="true"/>
<many-to-onename="address"
column="addressId"
not-null="true"/>
</join>
</class>
<class name="Address">
<id name="id"column="addressId">
<generatorclass="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primarykey, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
基於鏈接表的單向一對一關聯很是少見,但也是可行的。
<class name="Person">
<idname="id" column="personId">
<generatorclass="native"/>
</id>
<jointable="PersonAddress"
optional="true">
<keycolumn="personId"
unique="true"/>
<many-to-onename="address"
column="addressId"
not-null="true"
unique="true"/>
</join>
</class>
<class name="Address">
<idname="id" column="addressId">
<generatorclass="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primarykey, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
最後,還有 單向多對多關聯.
<class name="Person">
<idname="id" column="personId">
<generatorclass="native"/>
</id>
<setname="addresses" table="PersonAddress">
<keycolumn="personId"/>
<many-to-manycolumn="addressId"
class="Address"/>
</set>
</class>
<class name="Address">
<idname="id" column="addressId">
<generatorclass="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressIdbigint not null, primary key (personId, addressId) )
create table Address ( addressId bigint not null primary key )
7.4. 雙向關聯(Bidirectional associations)
7.4.1. 一對多(one to many) / 多對一(many to one)
雙向多對一關聯 是最多見的關聯關係。(這也是標準的父/子關聯關係。)
<class name="Person">
<idname="id" column="personId">
<generatorclass="native"/>
</id>
<many-to-onename="address"
column="addressId"
not-null="true"/>
</class>
<class name="Address">
<idname="id" column="addressId">
<generatorclass="native"/>
</id>
<setname="people" inverse="true">
<keycolumn="addressId"/>
<one-to-manyclass="Person"/>
</set>
</class>
create table Person ( personId bigint not null primary key,addressId bigint not null )
create table Address ( addressId bigint not null primary key )
若是你使用List(或者其餘有序集合類),你須要設置外鍵對應的key列爲 not null,讓Hibernate來從集合端管理關聯,維護每一個元素的索引(經過設置update="false" and insert="false"來對另外一端反向操做)。
<class name="Person">
<idname="id"/>
...
<many-to-onename="address"
column="addressId"
not-null="true"
insert="false"
update="false"/>
</class>
<class name="Address">
<idname="id"/>
...
<listname="people">
<keycolumn="addressId" not-null="true"/>
<list-indexcolumn="peopleIdx"/>
<one-to-manyclass="Person"/>
</list>
</class>
倘若集合映射的<key>元素對應的底層外鍵字段是NOT NULL的,那麼爲這一key元素定義not-null="true"是很重要的。不要僅僅爲可能的嵌套<column>元素定義not-null="true",<key>元素也是須要的。
基於外鍵關聯的雙向一對一關聯也很常見。
<class name="Person">
<idname="id" column="personId">
<generatorclass="native"/>
</id>
<many-to-onename="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<class name="Address">
<idname="id" column="addressId">
<generatorclass="native"/>
</id>
<one-to-onename="person"
property-ref="address"/>
</class>
create table Person ( personId bigint not null primary key,addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
基於主鍵關聯的一對一關聯須要使用特定的id生成器。
<class name="Person">
<idname="id" column="personId">
<generatorclass="native"/>
</id>
<one-to-onename="address"/>
</class>
<class name="Address">
<idname="id" column="personId">
<generatorclass="foreign">
<paramname="property">person</param>
</generator>
</id>
<one-to-onename="person"
constrained="true"/>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )
7.5. 使用鏈接表的雙向關聯(Bidirectional associations with join tables)
7.5.1. 一對多(one to many) /多對一(many to one)
基於鏈接表的雙向一對多關聯。注意inverse="true"能夠出如今關聯的任意一端,即collection端或者join端。
<class name="Person">
<idname="id" column="personId">
<generatorclass="native"/>
</id>
<setname="addresses"
table="PersonAddress">
<keycolumn="personId"/>
<many-to-manycolumn="addressId"
unique="true"
class="Address"/>
</set>
</class>
<class name="Address">
<idname="id" column="addressId">
<generatorclass="native"/>
</id>
<jointable="PersonAddress"
inverse="true"
optional="true">
<keycolumn="addressId"/>
<many-to-onename="person"
column="personId"
not-null="true"/>
</join>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressIdbigint not null primary key )
create table Address ( addressId bigint not null primary key )
基於鏈接表的雙向一對一關聯極爲罕見,但也是可行的。
<class name="Person">
<idname="id" column="personId">
<generatorclass="native"/>
</id>
<jointable="PersonAddress"
optional="true">
<keycolumn="personId"
unique="true"/>
<many-to-onename="address"
column="addressId"
not-null="true"
unique="true"/>
</join>
</class>
<class name="Address">
<idname="id" column="addressId">
<generatorclass="native"/>
</id>
<jointable="PersonAddress"
optional="true"
inverse="true">
<keycolumn="addressId"
unique="true"/>
<many-to-onename="person"
column="personId"
not-null="true"
unique="true"/>
</join>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primarykey, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
最後,還有 雙向多對多關聯.
<class name="Person">
<idname="id" column="personId">
<generatorclass="native"/>
</id>
<setname="addresses" table="PersonAddress">
<keycolumn="personId"/>
<many-to-manycolumn="addressId"
class="Address"/>
</set>
</class>
<class name="Address">
<idname="id" column="addressId">
<generatorclass="native"/>
</id>
<setname="people" inverse="true" table="PersonAddress">
<keycolumn="addressId"/>
<many-to-manycolumn="personId"
class="Person"/>
</set>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressIdbigint not null, primary key (personId, addressId) )
create table Address ( addressId bigint not null primary key )
更復雜的關聯鏈接極爲罕見。經過在映射文檔中嵌入SQL片段,Hibernate也能夠處理更爲複雜的狀況。好比,倘若包含歷史賬戶數據的表定義了accountNumber, effectiveEndDate 和effectiveStartDate字段,按照下面映射:
<properties name="currentAccountKey">
<propertyname="accountNumber" type="string"not-null="true"/>
<propertyname="currentAccount" type="boolean">
<formula>case when effectiveEndDate is null then 1 else 0end</formula>
</property>
</properties>
<property name="effectiveEndDate"type="date"/>
<property name="effectiveStateDate"type="date" not-null="true"/>
那麼咱們能夠對目前(current)實例(其effectiveEndDate爲null)使用這樣的關聯映射:
<many-to-one name="currentAccountInfo"
property-ref="currentAccountKey"
class="AccountInfo">
<columnname="accountNumber"/>
<formula>'1'</formula>
</many-to-one>
更復雜的例子,假想Employee和Organization之間的關聯是經過一個Employment中間表維護的,而中間表中填充了不少歷史僱員數據。那「僱員的最新僱主」這個關聯(最新僱主就是startDate最後的那個)能夠這樣映射:
<join>
<keycolumn="employeeId"/>
<subselect>
select employeeId,orgId
from Employments
group by orgId
having startDate =max(startDate)
</subselect>
<many-to-onename="mostRecentEmployer"
class="Organization"
column="orgId"/>
</join>
使用這一功能時能夠充滿創意,但一般更加實用的是用HQL或條件查詢來處理這些情形。
組件(Component)這個概念在Hibernate中幾處不一樣的地方爲了避免同的目的被重複使用.
組件(Component)是一個被包含的對象,在持久化的過程當中,它被看成值類型,而並不是一個實體的引用。在這篇文檔中,組件這一術語指的是面向對象的合成概念(而並非系統構架層次上的組件的概念)。舉個例子, 你對人(Person)這個概念能夠像下面這樣來建模:
public class Person {
private java.util.Datebirthday;
private Name name;
private String key;
public String getKey(){
return key;
}
private voidsetKey(String key) {
this.key=key;
}
public java.util.DategetBirthday() {
return birthday;
}
public voidsetBirthday(java.util.Date birthday) {
this.birthday =birthday;
}
public Name getName(){
return name;
}
public voidsetName(Name name) {
this.name = name;
}
......
......
}
public class Name {
char initial;
String first;
String last;
public StringgetFirst() {
return first;
}
void setFirst(Stringfirst) {
this.first =first;
}
public StringgetLast() {
return last;
}
void setLast(Stringlast) {
this.last = last;
}
public chargetInitial() {
return initial;
}
void setInitial(charinitial) {
this.initial =initial;
}
}
在持久化的過程當中,姓名(Name)能夠做爲人(Person)的一個組件。須要注意的是:你應該爲姓名的持久化屬性定義getter和setter方法,可是你不須要實現任何的接口或申明標識符字段。
如下是這個例子的Hibernate映射文件:
<class name="eg.Person"table="person">
<idname="Key" column="pid" type="string">
<generatorclass="uuid"/>
</id>
<propertyname="birthday" type="date"/>
<componentname="Name" class="eg.Name"> <!-- class attributeoptional -->
<propertyname="initial"/>
<propertyname="first"/>
<propertyname="last"/>
</component>
</class>
人員(Person)表中將包括pid, birthday, initial, first和 last等字段。
就像全部的值類型同樣, 組件不支持共享引用。換句話說,兩我的可能重名,可是兩個Person對象應該包含兩個獨立的Name對象,只不過這兩個Name對象具備「一樣」的值。組件的值能夠爲空,其定義以下。每當Hibernate從新加載一個包含組件的對象,若是該組件的全部字段爲空,Hibernate將假定整個組件爲空。在大多數狀況下,這樣假定應該是沒有問題的。
組件的屬性能夠是任意一種Hibernate類型(包括集合, 多對多關聯,以及其它組件等等)。嵌套組件不該該被看成一種特殊的應用(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">
<generatorclass="uuid"/>
</id>
<propertyname="birthday" type="date">
<componentname="Name" class="eg.Name" unique="true">
<parentname="namedPerson"/> <!-- reference back to the Person -->
<propertyname="initial"/>
<propertyname="first"/>
<propertyname="last"/>
</component>
</class>
8.2. 在集合中出現的依賴對象 (Collections of dependent objects)
Hibernate支持組件的集合(例如: 一個元素是姓名(Name)這種類型的數組)。你能夠使用<composite-element>標籤替代<element>標籤來定義你的組件集合。
<set name="someNames" table="some_names"lazy="true">
<keycolumn="id"/>
<composite-elementclass="eg.Name"> <!-- class attribute required -->
<propertyname="initial"/>
<propertyname="first"/>
<propertyname="last"/>;
</composite-element>
</set>
注意,若是你定義的Set包含組合元素(composite-element),正確地實現equals()和hashCode()是很是重要的。
組合元素能夠包含組件,可是不能包含集合。若是你的組合元素自身包含組件, 你必須使用<nested-composite-element>標籤。這是一個至關特殊的案例 - 在一個組件的集合裏,那些組件自己又能夠包含其餘的組件。這個時候你就應該考慮一下使用one-to-many關聯是否會更恰當。嘗試對這個組合元素從新建模爲一個實體-可是須要注意的是,雖然Java模型和從新建模前是同樣的,關係模型和持久性語義會有細微的變化。
請注意若是你使用<set>標籤,一個組合元素的映射不支持可能爲空的屬性. 當刪除對象時,Hibernate必須使用每個字段的值來肯定一條記錄(在組合元素表中,沒有單獨的關鍵字段),若是有爲null的字段,這樣作就不可能了。你必須做出一個選擇,要麼在組合元素中使用不能爲空的屬性,要麼選擇使用<list>,<map>,<bag> 或者 <idbag>而不是 <set>。
組合元素有個特別的用法是它能夠包含一個<many-to-one>元素。相似這樣的映射容許你將一個many-to-many關聯表映射爲組合元素的集合。(Amapping like this allows you to map extra columns of a many-to-many associationtable to the composite element class.) 接下來的的例子是從Order到Item的一個多對多的關聯關係, 關聯屬性是purchaseDate, price 和 quantity 。
<class name="eg.Order" .... >
....
<setname="purchasedItems" table="purchase_items"lazy="true">
<keycolumn="order_id">
<composite-element class="eg.Purchase">
<propertyname="purchaseDate"/>
<propertyname="price"/>
<propertyname="quantity"/>
<many-to-one name="item" class="eg.Item"/><!-- class attribute is optional -->
</composite-element>
</set>
</class>
固然,當你定義Item時,你沒法引用這些purchase,所以你沒法實現雙向關聯查詢。記住組件是值類型,而且不容許共享引用。某一個特定的Purchase 能夠放在Order的集合中,但它不能同時被Item所引用。
其實組合元素的這個用法能夠擴展到三重或多重關聯:
<class name="eg.Order" .... >
....
<setname="purchasedItems" table="purchase_items"lazy="true">
<keycolumn="order_id">
<composite-elementclass="eg.OrderLine">
<many-to-one name="purchaseDetails"class="eg.Purchase"/>
<many-to-one name="item" class="eg.Item"/>
</composite-element>
</set>
</class>
在查詢中,表達組合元素的語法和關聯到其餘實體的語法是同樣的。
8.3. 組件做爲Map的索引(Componentsas Map indices )
<composite-map-key>元素容許你映射一個組件類做爲一個Map的key,前提是你必須正確的在這個類中重寫了hashCode() 和 equals()方法。
8.4. 組件做爲聯合標識符(Components as composite identifiers)
你能夠使用一個組件做爲一個實體類的標識符。你的組件類必須知足如下要求:
注意:在Hibernate3中,第二個要求並不是是Hibernate強制必須的。但最好這樣作。
你不能使用一個IdentifierGenerator產生組合關鍵字。一個應用程序必須分配它本身的標識符。
使用<composite-id> 標籤(而且內嵌<key-property>元素)代替一般的<id>標籤。好比,OrderLine類具備一個主鍵,這個主鍵依賴於Order的(聯合)主鍵。
<class name="OrderLine">
<composite-idname="id" class="OrderLineId">
<key-propertyname="lineId"/>
<key-propertyname="orderId"/>
<key-propertyname="customerId"/>
</composite-id>
<propertyname="name"/>
<many-to-onename="order" class="Order"
insert="false" update="false">
<columnname="orderId"/>
<columnname="customerId"/>
</many-to-one>
....
</class>
如今,任何指向OrderLine的外鍵都是複合的。在你的映射文件中,必須爲其餘類也這樣聲明。例如,一個指向OrderLine的關聯可能被這樣映射:
<many-to-one name="orderLine"class="OrderLine">
<!-- the "class" attribute is optional, as usual-->
<columnname="lineId"/>
<columnname="orderId"/>
<columnname="customerId"/>
</many-to-one>
(注意在各個地方<column>標籤都是column屬性的替代寫法。)
指向OrderLine的多對多關聯也使用聯合外鍵:
<set name="undeliveredOrderLines">
<key columnname="warehouseId"/>
<many-to-manyclass="OrderLine">
<columnname="lineId"/>
<columnname="orderId"/>
<columnname="customerId"/>
</many-to-many>
</set>
在Order中,OrderLine的集合則是這樣:
<set name="orderLines" inverse="true">
<key>
<columnname="orderId"/>
<columnname="customerId"/>
</key>
<one-to-manyclass="OrderLine"/>
</set>
(與一般同樣,<one-to-many>元素不聲明任何列.)
倘若OrderLine自己擁有一個集合,它也具備組合外鍵。
<class name="OrderLine">
....
....
<listname="deliveryAttempts">
<key> <!-- a collection inherits the compositekey type -->
<columnname="lineId"/>
<columnname="orderId"/>
<columnname="customerId"/>
</key>
<list-indexcolumn="attemptId" base="1"/>
<composite-element class="DeliveryAttempt">
...
</composite-element>
</set>
</class>
你甚至能夠映射Map類型的屬性:
<dynamic-component name="userAttributes">
<propertyname="foo" column="FOO" type="string"/>
<propertyname="bar" column="BAR" type="integer"/>
<many-to-onename="baz" class="Baz" column="BAZ_ID"/>
</dynamic-component>
從<dynamic-component>映射的語義上來說,它和<component>是相同的。這種映射類型的優勢在於經過修改映射文件,就能夠具備在部署時檢測真實屬性的能力。利用一個DOM解析器,也能夠在程序運行時操做映射文件。更好的是,你能夠經過Configuration對象來訪問(或者修改)Hibernate的運行時元模型。
第 9 章 繼承映射(InheritanceMappings)
Hibernate支持三種基本的繼承映射策略:
此外,Hibernate還支持第四種稍有不一樣的多態映射策略:
對於同一個繼承層次內的不一樣分支,能夠採用不一樣的映射策略,而後用隱式多態來完成跨越整個層次的多態。可是在同一個<class>根元素下,Hibernate不支持混合了元素<subclass>、 <joined-subclass>和<union-subclass> 的映射。在同一個<class>元素下,能夠混合使用「每一個類分層結構一張表」(table per hierarchy)和「每一個子類一張表」(table per subclass)這兩種映射策略,這是經過結合元素<subclass>和 <join>來實現的(見後)。
在多個映射文件中,能夠直接在hibernate-mapping根下定義subclass,union-subclass和joined-subclass。也就是說,你能夠僅加入一個新的映射文件來擴展類層次。你必須在subclass的映射中指明extends屬性,給出一個以前定義的超類的名字。注意,在之前,這一功能對映射文件的順序有嚴格的要求,從Hibernate 3開始,使用extends關鍵字的時侯,對映射文件的順序再也不有要求;但在每一個映射文件裏,超類必須在子類以前定義。
<hibernate-mapping>
<subclassname="DomesticCat" extends="Cat"discriminator-value="D">
<propertyname="name" type="string"/>
</subclass>
</hibernate-mapping>
9.1.1. 每一個類分層結構一張表(Table per class hierarchy)
假設咱們有接口Payment和它的幾個實現類: CreditCardPayment, CashPayment, 和ChequePayment。則「每一個類分層結構一張表」(Tableper class hierarchy)的映射代碼以下所示:
<class name="Payment" table="PAYMENT">
<idname="id" type="long" column="PAYMENT_ID">
<generatorclass="native"/>
</id>
<discriminatorcolumn="PAYMENT_TYPE" type="string"/>
<propertyname="amount" column="AMOUNT"/>
...
<subclass name="CreditCardPayment"discriminator-value="CREDIT">
<propertyname="creditCardType" column="CCTYPE"/>
...
</subclass>
<subclassname="CashPayment" discriminator-value="CASH">
...
</subclass>
<subclassname="ChequePayment" discriminator-value="CHEQUE">
...
</subclass>
</class>
採用這種策略只須要一張表便可。它有一個很大的限制:要求那些由子類定義的字段,如CCTYPE,不能有非空(NOT NULL)約束。
9.1.2. 每一個子類一張表(Table per subclass)
對於上例中的幾個類而言,採用「每一個子類一張表」的映射策略,代碼以下所示:
<class name="Payment" table="PAYMENT">
<idname="id" type="long" column="PAYMENT_ID">
<generatorclass="native"/>
</id>
<propertyname="amount" column="AMOUNT"/>
...
<joined-subclassname="CreditCardPayment" table="CREDIT_PAYMENT">
<keycolumn="PAYMENT_ID"/>
...
</joined-subclass>
<joined-subclassname="CashPayment" table="CASH_PAYMENT">
<keycolumn="PAYMENT_ID"/>
<propertyname="creditCardType" column="CCTYPE"/>
...
</joined-subclass>
<joined-subclassname="ChequePayment" table="CHEQUE_PAYMENT">
<keycolumn="PAYMENT_ID"/>
...
</joined-subclass>
</class>
須要四張表。三個子類表經過主鍵關聯到超類表(於是關係模型其實是一對一關聯)。
9.1.3. 每一個子類一張表(Table per subclass),使用辨別標誌(Discriminator)
注意,對「每一個子類一張表」的映射策略,Hibernate的實現不須要辨別字段,而其餘的對象/關係映射工具使用了一種不一樣於Hibernate的實現方法,該方法要求在超類表中有一個類型辨別字段(typediscriminator column)。Hibernate採用的方法更難實現,但從關係(數據庫)的角度來看,按理說它更正確。若你願意使用帶有辨別字段的「每一個子類一張表」的策略,你能夠結合使用<subclass> 與<join>,以下所示:
<class name="Payment" table="PAYMENT">
<idname="id" type="long" column="PAYMENT_ID">
<generatorclass="native"/>
</id>
<discriminatorcolumn="PAYMENT_TYPE" type="string"/>
<propertyname="amount" column="AMOUNT"/>
...
<subclassname="CreditCardPayment" discriminator-value="CREDIT">
<jointable="CREDIT_PAYMENT">
<keycolumn="PAYMENT_ID"/>
<propertyname="creditCardType" column="CCTYPE"/>
...
</join>
</subclass>
<subclassname="CashPayment" discriminator-value="CASH">
<jointable="CASH_PAYMENT">
<keycolumn="PAYMENT_ID"/>
...
</join>
</subclass>
<subclassname="ChequePayment" discriminator-value="CHEQUE">
<jointable="CHEQUE_PAYMENT" fetch="select">
<keycolumn="PAYMENT_ID"/>
...
</join>
</subclass>
</class>
可選的聲明fetch="select",是用來告訴Hibernate,在查詢超類時,不要使用外部鏈接(outer join)來抓取子類ChequePayment的數據。
9.1.4. 混合使用「每一個類分層結構一張表」和「每一個子類一張表」
你甚至能夠採起以下方法混和使用「每一個類分層結構一張表」和「每一個子類一張表」這兩種策略:
<class name="Payment" table="PAYMENT">
<idname="id" type="long" column="PAYMENT_ID">
<generatorclass="native"/>
</id>
<discriminatorcolumn="PAYMENT_TYPE" type="string"/>
<propertyname="amount" column="AMOUNT"/>
...
<subclassname="CreditCardPayment" discriminator-value="CREDIT">
<jointable="CREDIT_PAYMENT">
<propertyname="creditCardType" column="CCTYPE"/>
...
</join>
</subclass>
<subclassname="CashPayment" discriminator-value="CASH">
...
</subclass>
<subclassname="ChequePayment" discriminator-value="CHEQUE">
...
</subclass>
</class>
對上述任何一種映射策略而言,指向根類Payment的關聯是使用<many-to-one>進行映射的。
<many-to-one name="payment"column="PAYMENT_ID" class="Payment"/>
9.1.5. 每一個具體類一張表(Table per concrete class)
對於「每一個具體類一張表」的映射策略,能夠採用兩種方法。第一種方法是使用 <union-subclass>。
<class name="Payment">
<idname="id" type="long" column="PAYMENT_ID">
<generatorclass="sequence"/>
</id>
<propertyname="amount" column="AMOUNT"/>
...
<union-subclassname="CreditCardPayment" table="CREDIT_PAYMENT">
<propertyname="creditCardType" column="CCTYPE"/>
...
</union-subclass>
<union-subclassname="CashPayment" table="CASH_PAYMENT">
...
</union-subclass>
<union-subclassname="ChequePayment" table="CHEQUE_PAYMENT">
...
</union-subclass>
</class>
這裏涉及三張與子類相關的表。每張表爲對應類的全部屬性(包括從超類繼承的屬性)定義相應字段。
這種方式的侷限在於,若是一個屬性在超類中作了映射,其字段名必須與全部子類表中定義的相同。(咱們可能會在Hibernate的後續發佈版本中放寬此限制。)不容許在聯合子類(union subclass)的繼承層次中使用標識生成器策略(identity generator strategy), 實際上, 主鍵的種子(primarykey seed)不得不爲同一繼承層次中的所有被聯合子類所共用.
倘若超類是抽象類,請使用abstract="true"。固然,倘若它不是抽象的,須要一個額外的表(上面的例子中,默認是PAYMENT),來保存超類的實例。
9.1.6. Tableper concrete class, using implicit polymorphism
9.1.6. Table per concrete class, using implicitpolymorphism
另外一種可供選擇的方法是採用隱式多態:
<class name="CreditCardPayment" table="CREDIT_PAYMENT">
<idname="id" type="long"column="CREDIT_PAYMENT_ID">
<generatorclass="native"/>
</id>
<propertyname="amount" column="CREDIT_AMOUNT"/>
...
</class>
<class name="CashPayment"table="CASH_PAYMENT">
<idname="id" type="long"column="CASH_PAYMENT_ID">
<generatorclass="native"/>
</id>
<propertyname="amount" column="CASH_AMOUNT"/>
...
</class>
<class name="ChequePayment"table="CHEQUE_PAYMENT">
<idname="id" type="long" column="CHEQUE_PAYMENT_ID">
<generatorclass="native"/>
</id>
<propertyname="amount" column="CHEQUE_AMOUNT"/>
...
</class>
注意,咱們沒有在任何地方明確的說起接口Payment。同時注意 Payment的屬性在每一個子類中都進行了映射。若是你想避免重複,能夠考慮使用XML實體(例如:位於DOCTYPE聲明內的 [<!ENTITY allproperties SYSTEM "allproperties.xml"> ] 和映射中的&allproperties;)。
這種方法的缺陷在於,在Hibernate執行多態查詢時(polymorphicqueries)沒法生成帶 UNION的SQL語句。
對於這種映射策略而言,一般用<any>來實現到 Payment的多態關聯映射。
<any name="payment" meta-type="string"id-type="long">
<meta-valuevalue="CREDIT" class="CreditCardPayment"/>
<meta-valuevalue="CASH" class="CashPayment"/>
<meta-valuevalue="CHEQUE" class="ChequePayment"/>
<columnname="PAYMENT_CLASS"/>
<columnname="PAYMENT_ID"/>
</any>
對這一映射還有一點須要注意。由於每一個子類都在各自獨立的元素<class> 中映射(而且Payment只是一個接口),每一個子類能夠很容易的成爲另外一個繼承體系中的一部分!(你仍然能夠對接口Payment使用多態查詢。)
<class name="CreditCardPayment"table="CREDIT_PAYMENT">
<idname="id" type="long"column="CREDIT_PAYMENT_ID">
<generatorclass="native"/>
</id>
<discriminatorcolumn="CREDIT_CARD" type="string"/>
<propertyname="amount" column="CREDIT_AMOUNT"/>
...
<subclassname="MasterCardPayment" discriminator-value="MDC"/>
<subclassname="VisaPayment" discriminator-value="VISA"/>
</class>
<class name="NonelectronicTransaction"table="NONELECTRONIC_TXN">
<idname="id" type="long" column="TXN_ID">
<generatorclass="native"/>
</id>
...
<joined-subclassname="CashPayment" table="CASH_PAYMENT">
<keycolumn="PAYMENT_ID"/>
<propertyname="amount" column="CASH_AMOUNT"/>
...
</joined-subclass>
<joined-subclassname="ChequePayment" table="CHEQUE_PAYMENT">
<keycolumn="PAYMENT_ID"/>
<propertyname="amount" column="CHEQUE_AMOUNT"/>
...
</joined-subclass>
</class>
咱們仍是沒有明確的提到Payment。若是咱們針對接口Payment執行查詢——如from Payment—— Hibernate 自動返回CreditCardPayment(和它的子類,由於它們也實現了接口Payment)、 CashPayment和Chequepayment的實例,但不返回NonelectronicTransaction的實例。
對「每一個具體類映射一張表」(table per concrete-class)的映射策略而言,隱式多態的方式有必定的限制。而<union-subclass>映射的限制則沒有那麼嚴格。
下面表格中列出了在Hibernte中「每一個具體類一張表」的策略和隱式多態的限制。
表 9.1. 繼承映射特性(Features of inheritance mappings)
繼承策略(Inheritance strategy) |
多態多對一 |
多態一對一 |
多態一對多 |
多態多對多 |
多態 load()/get() |
多態查詢 |
多態鏈接(join) |
外鏈接(Outer join)讀取 |
每一個類分層結構一張表 |
<many-to-one> |
<one-to-one> |
<one-to-many> |
<many-to-many> |
s.get(Payment.class, id) |
from Payment p |
from Order o join o.payment p |
支持 |
每一個子類一張表 |
<many-to-one> |
<one-to-one> |
<one-to-many> |
<many-to-many> |
s.get(Payment.class, id) |
from Payment p |
from Order o join o.payment p |
支持 |
每一個具體類一張表(union-subclass) |
<many-to-one> |
<one-to-one> |
<one-to-many> (僅對於inverse="true"的狀況) |
<many-to-many> |
s.get(Payment.class, id) |
from Payment p |
from Order o join o.payment p |
支持 |
每一個具體類一張表(隱式多態) |
<any> |
不支持 |
不支持 |
<many-to-any> |
s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult() |
from Payment p |
不支持 |
不支持 |
Hibernate是完整的對象/關係映射解決方案,它提供了對象狀態管理(state management)的功能,使開發者再也不須要理會底層數據庫系統的細節。也就是說,相對於常見的JDBC/SQL持久層方案中須要管理SQL語句,Hibernate採用了更天然的面向對象的視角來持久化Java應用中的數據。
換句話說,使用Hibernate的開發者應該老是關注對象的狀態(state),沒必要考慮SQL語句的執行。這部分細節已經由Hibernate掌管穩當,只有開發者在進行系統性能調優的時候才須要進行了解。
10.1. Hibernate對象狀態(object states)
Hibernate定義並支持下列對象狀態(state):
接下來咱們來細緻的討論下狀態(states)及狀態間的轉換(statetransitions)(以及觸發狀態轉換的Hibernate方法)。
Hibernate認爲持久化類(persistent class)新實例化的對象是瞬時(Transient)的。咱們可經過將瞬時(Transient)對象與session關聯而把它變爲持久(Persistent)的。
DomesticCat fritz = new DomesticCat();
fritz.setColor(Color.GINGER);
fritz.setSex('M');
fritz.setName("Fritz");
Long generatedId = (Long) sess.save(fritz);
若是Cat的持久化標識(identifier)是generated類型的,那麼該標識(identifier)會自動在save()被調用時產生並分配給cat。若是Cat的持久化標識(identifier)是assigned類型的,或是一個複合主鍵(composite key),那麼該標識(identifier)應當在調用save()以前手動賦予給cat。你也能夠按照EJB3 early draft中定義的語義,使用persist()替代save()。
此外,你能夠用一個重載版本的save()方法。
DomesticCat pk = new DomesticCat();
pk.setColor(Color.TABBY);
pk.setSex('F');
pk.setName("PK");
pk.setKittens( new HashSet() );
pk.addKitten(fritz);
sess.save( pk, new Long(1234) );
若是你持久化的對象有關聯的對象(associated objects)(例如上例中的kittens集合)那麼對這些對象(譯註:pk和kittens)進行持久化的順序是任意的(也就是說能夠先對kittens進行持久化也能夠先對pk進行持久化),除非你在外鍵列上有NOTNULL約束。 Hibernate不會違反外鍵約束,可是若是你用錯誤的順序持久化對象(譯註:在pk持久化以前持久化kitten),那麼可能會違反NOT NULL約束。
一般你不會爲這些細節煩心,由於你極可能會使用Hibernate的 傳播性持久化(transitive persistence)功能自動保存相關聯那些對象。這樣連違反NOTNULL約束的狀況都不會出現了 - Hibernate會管好全部的事情。傳播性持久化(transitive persistence)將在本章稍後討論。
若是你知道某個實例的持久化標識(identifier),你就能夠使用Session的load()方法來獲取它。 load()的另外一個參數是指定類的.class對象。本方法會建立指定類的持久化實例,並從數據庫加載其數據(state)。
Cat fritz = (Cat) sess.load(Cat.class, generatedId);
// you need to wrap primitive identifiers
long id = 1234;
DomesticCat pk = (DomesticCat) sess.load( DomesticCat.class, newLong(id) );
此外, 你能夠把數據(state)加載到指定的對象實例上(覆蓋掉該實例原來的數據)。
Cat cat = new DomesticCat();
// load pk's state into cat
sess.load( cat, new Long(pkId) );
Set kittens = cat.getKittens();
請注意若是沒有匹配的數據庫記錄,load()方法可能拋出沒法恢復的異常(unrecoverable exception)。若是類的映射使用了代理(proxy),load()方法會返回一個未初始化的代理,直到你調用該代理的某方法時纔會去訪問數據庫。若你但願在某對象中建立一個指向另外一個對象的關聯,又不想在從數據庫中裝載該對象時同時裝載相關聯的那個對象,那麼這種操做方式就用得上的了。若是爲相應類映射關係設置了batch-size,那麼使用這種操做方式容許多個對象被一批裝載(由於返回的是代理,無需從數據庫中抓取全部對象的數據)。
若是你不肯定是否有匹配的行存在,應該使用get()方法,它會馬上訪問數據庫,若是沒有對應的記錄,會返回null。
Cat cat = (Cat) sess.get(Cat.class, id);
if (cat==null) {
cat = new Cat();
sess.save(cat, id);
}
return cat;
你甚至能夠選用某個LockMode,用SQL的SELECT... FOR UPDATE裝載對象。請查閱API文檔以獲取更多信息。
Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);
注意,任何關聯的對象或者包含的集合都不會被以FOR UPDATE方式返回,除非你指定了lock或者all做爲關聯(association)的級聯風格(cascadestyle)。
任什麼時候候均可以使用refresh()方法強迫裝載對象和它的集合。若是你使用數據庫觸發器功能來處理對象的某些屬性,這個方法就頗有用了。
sess.save(cat);
sess.flush(); //force the SQL INSERT
sess.refresh(cat); //re-read the state (after the triggerexecutes)
此處一般會出現一個重要問題: Hibernate會從數據庫中裝載多少東西?會執行多少條相應的SQLSELECT語句?這取決於抓取策略(fetching strategy),會在第 19.1 節「抓取策略(Fetching strategies) 」中解釋。
若是不知道所要尋找的對象的持久化標識,那麼你須要使用查詢。Hibernate支持強大且易於使用的面向對象查詢語言(HQL)。若是但願經過編程的方式建立查詢,Hibernate提供了完善的按條件(QueryBy Criteria, QBC)以及按樣例(QueryBy Example, QBE)進行查詢的功能。你也能夠用原生SQL(nativeSQL)描述查詢,Hibernate額外提供了將結果集(resultset)轉化爲對象的支持。
HQL和原生SQL(native SQL)查詢要經過爲org.hibernate.Query的實例來表達。這個接口提供了參數綁定、結果集處理以及運行實際查詢的方法。你老是能夠經過當前Session獲取一個Query對象:
List cats = session.createQuery(
"from Cat as catwhere cat.birthdate < ?")
.setDate(0, date)
.list();
List mothers = session.createQuery(
"select motherfrom Cat as cat join cat.mother as mother where cat.name = ?")
.setString(0, name)
.list();
List kittens = session.createQuery(
"from Cat as catwhere cat.mother = ?")
.setEntity(0, pk)
.list();
Cat mother = (Cat) session.createQuery(
"selectcat.mother from Cat as cat where cat = ?")
.setEntity(0, izi)
.uniqueResult();]]
Query mothersWithKittens = (Cat) session.createQuery(
"select motherfrom Cat as mother left join fetch mother.kittens");
Set uniqueMothers = new HashSet(mothersWithKittens.list());
一個查詢一般在調用list()時被執行,執行結果會徹底裝載進內存中的一個集合(collection)。查詢返回的對象處於持久(persistent)狀態。若是你知道的查詢只會返回一個對象,可以使用list()的快捷方式uniqueResult()。注意,使用集合預先抓取的查詢每每會返回屢次根對象(他們的集合類都被初始化了)。你能夠經過一個集合來過濾這些重複對象。
10.4.1.1. 迭代式獲取結果(Iterating results)
某些狀況下,你能夠使用iterate()方法獲得更好的性能。這一般是你預期返回的結果在session,或二級緩存(second-level cache)中已經存在時的狀況。如若否則,iterate()會比list()慢,並且可能簡單查詢也須要進行屢次數據庫訪問: iterate()會首先使用1條語句獲得全部對象的持久化標識(identifiers),再根據持久化標識執行n條附加的select語句實例化實際的對象。
// fetch ids
Iterator iter = sess.createQuery("from eg.Qux q order byq.likeliness").iterate();
while ( iter.hasNext() ) {
Qux qux = (Qux)iter.next(); // fetch the object
// something wecouldnt express in the query
if (qux.calculateComplicatedAlgorithm() ) {
// delete thecurrent instance
iter.remove();
// dont need toprocess the rest
break;
}
}
(譯註:元組(tuples)指一條結果行包含多個對象)Hibernate查詢有時返回元組(tuples),每一個元組(tuples)以數組的形式返回:
Iterator kittensAndMothers = sess.createQuery(
"selectkitten, mother from Cat kitten join kitten.mother mother")
.list()
.iterator();
while ( kittensAndMothers.hasNext() ) {
Object[] tuple =(Object[]) kittensAndMothers.next();
Cat kitten = tuple[0];
Cat mother = tuple[1];
....
}
查詢可在select從句中指定類的屬性,甚至能夠調用SQL統計(aggregate)函數。屬性或統計結果被認定爲"標量(Scalar)"的結果(而不是持久(persistent state)的實體)。
Iterator results = sess.createQuery(
"selectcat.color, min(cat.birthdate), count(cat) from Cat cat " +
"group bycat.color")
.list()
.iterator();
while ( results.hasNext() ) {
Object[] row =(Object[]) results.next();
Color type = (Color)row[0];
Date oldest = (Date)row[1];
Integer count =(Integer) row[2];
.....
}
接口Query提供了對命名參數(named parameters)、JDBC風格的問號(?)參數進行綁定的方法。 不一樣於JDBC,Hibernate對參數從0開始計數。 命名參數(named parameters)在查詢字符串中是形如:name的標識符。命名參數(named parameters)的優勢是:
//named parameter (preferred)
Query q = sess.createQuery("from DomesticCat cat wherecat.name = :name");
q.setString("name", "Fritz");
Iterator cats = q.iterate();
//positional parameter
Query q = sess.createQuery("from DomesticCat cat wherecat.name = ?");
q.setString(0, "Izi");
Iterator cats = q.iterate();
//named parameter list
List names = new ArrayList();
names.add("Izi");
names.add("Fritz");
Query q = sess.createQuery("from DomesticCat cat wherecat.name in (:namesList)");
q.setParameterList("namesList", names);
List cats = q.list();
若是你須要指定結果集的範圍(但願返回的最大行數/或開始的行數),應該使用Query接口提供的方法:
Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.list();
Hibernate 知道如何將這個有限定條件的查詢轉換成你的數據庫的原生SQL(native SQL)。
10.4.1.6. 可滾動遍歷(Scrollable iteration)
若是你的JDBC驅動支持可滾動的ResuleSet,Query接口能夠使用ScrollableResults,容許你在查詢結果中靈活遊走。
Query q = sess.createQuery("select cat.name, cat fromDomesticCat cat " +
"order by cat.name");
ScrollableResults cats = q.scroll();
if ( cats.first() ) {
// find the first nameon each page of an alphabetical list of cats by name
firstNamesOfPages =new ArrayList();
do {
String name =cats.getString(0);
firstNamesOfPages.add(name);
}
while (cats.scroll(PAGE_SIZE) );
// Now get the first page of cats
pageOfCats = newArrayList();
cats.beforeFirst();
int i=0;
while( ( PAGE_SIZE> i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );
}
cats.close()
請注意,使用此功能須要保持數據庫鏈接(以及遊標(cursor))處於一直打開狀態。若是你須要斷開鏈接使用分頁功能,請使用setMaxResult()/setFirstResult()
10.4.1.7. 外置命名查詢(Externalizing named queries)
你能夠在映射文件中定義命名查詢(named queries)。(若是你的查詢串中包含可能被解釋爲XML標記(markup)的字符,別忘了用CDATA包裹起來。)
<queryname="eg.DomesticCat.by.name.and.minimum.weight"><![CDATA[
from eg.DomesticCat ascat
where cat.name = ?
and cat.weight> ?
] ]></query>
參數綁定及執行以編程方式(programatically)完成:
Query q =sess.getNamedQuery("eg.DomesticCat.by.name.and.minimum.weight");
q.setString(0, name);
q.setInt(1, minWeight);
List cats = q.list();
請注意實際的程序代碼與所用的查詢語言無關,你也可在元數據中定義原生SQL(native SQL)查詢,或將原有的其餘的查詢語句放在配置文件中,這樣就可讓Hibernate統一管理,達到遷移的目的。
集合過濾器(filter)是一種用於一個持久化集合或者數組的特殊的查詢。查詢字符串中能夠使用"this"來引用集合中的當前元素。
Collection blackKittens = session.createFilter(
pk.getKittens(),
"where this.color= ?")
.setParameter(Color.BLACK, Hibernate.custom(ColorUserType.class) )
.list()
);
返回的集合能夠被認爲是一個包(bag, 無順序可重複的集合(collection)),它是所給集合的副本。原來的集合不會被改動(這與「過濾器(filter)」的隱含的含義不符,不過與咱們期待的行爲一致)。
請注意過濾器(filter)並不須要from子句(固然須要的話它們也能夠加上)。過濾器(filter)不限定於只能返回集合元素自己。
Collection blackKittenMates = session.createFilter(
pk.getKittens(),
"select this.matewhere this.color = eg.Color.BLACK.intValue")
.list();
即便無條件的過濾器(filter)也是有意義的。例如,用於加載一個大集合的子集:
Collection tenKittens = session.createFilter(
mother.getKittens(),"")
.setFirstResult(0).setMaxResults(10)
.list();
10.4.3. 條件查詢(Criteria queries)
HQL極爲強大,可是有些人但願可以動態的使用一種面向對象API建立查詢,而非在他們的Java代碼中嵌入字符串。對於那部分人來講,Hibernate提供了直觀的Criteria查詢API。
Criteria crit = session.createCriteria(Cat.class);
crit.add( Expression.eq( "color", eg.Color.BLACK ) );
crit.setMaxResults(10);
List cats = crit.list();
Criteria以及相關的樣例(Example)API將會再第 15 章 條件查詢(Criteria Queries) 中詳細討論。
你能夠使用createSQLQuery()方法,用SQL來描述查詢,並由Hibernate將結果集轉換成對象。請注意,你能夠在任什麼時候候調用session.connection()來得到並使用JDBC Connection對象。若是你選擇使用Hibernate的API, 你必須把SQL別名用大括號包圍起來:
List cats = session.createSQLQuery(
"SELECT {cat.*}FROM CAT {cat} WHERE ROWNUM<10",
"cat",
Cat.class
).list();
List cats = session.createSQLQuery(
"SELECT {cat}.IDAS {cat.id}, {cat}.SEX AS {cat.sex}, " +
"{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ..." +
"FROM CAT {cat}WHERE ROWNUM<10",
"cat",
Cat.class
).list()
和Hibernate查詢同樣,SQL查詢也能夠包含命名參數和佔位參數。能夠在第 16 章 Native SQL查詢找到更多關於Hibernate中原生SQL(nativeSQL)的信息。
事務中的持久實例(就是經過session裝載、保存、建立或者查詢出的對象)被應用程序操做所形成的任何修改都會在Session被刷出(flushed)的時候被持久化(本章後面會詳細討論)。這裏不須要調用某個特定的方法(好比update(),設計它的目的是不一樣的)將你的修改持久化。因此最直接的更新一個對象的方法就是在Session處於打開狀態時load()它,而後直接修改便可:
DomesticCat cat = (DomesticCat) sess.load( Cat.class, newLong(69) );
cat.setName("PK");
sess.flush(); // changesto cat are automatically detected and persisted
有時這種程序模型效率低下,由於它在同一Session裏須要一條SQL SELECT語句(用於加載對象)以及一條SQLUPDATE語句(持久化更新的狀態)。爲此Hibernate提供了另外一種途徑,使用脫管(detached)實例。
請注意Hibernate自己不提供直接執行UPDATE或DELETE語句的API。 Hibernate提供的是狀態管理(state management)服務,你沒必要考慮要使用的語句(statements)。 JDBC是出色的執行SQL語句的API,任什麼時候候調用session.connection()你均可以獲得一個JDBC Connection對象。此外,在聯機事務處理(OLTP)程序中,大量操做(mass operations)與對象/關係映射的觀點是相沖突的。 Hibernate的未來版本可能會提供專門的進行大量操做(mass operation)的功能。參考第 13 章 批量處理(Batch processing),尋找一些可用的批量(batch)操做技巧。
不少程序須要在某個事務中獲取對象,而後將對象發送到界面層去操做,最後在一個新的事務保存所作的修改。在高併發訪問的環境中使用這種方式,一般使用附帶版本信息的數據來保證這些「長「工做單元之間的隔離。
Hibernate經過提供Session.update()或Session.merge() 從新關聯脫管實例的辦法來支持這種模型。
// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catId);
Cat potentialMate = new Cat();
firstSession.save(potentialMate);
// in a higher layer of the application
cat.setMate(potentialMate);
// later, in a new session
secondSession.update(cat); // update cat
secondSession.update(mate); // update mate
若是具備catId持久化標識的Cat以前已經被另外一Session(secondSession)裝載了,應用程序進行重關聯操做(reattach)的時候會拋出一個異常。
若是你肯定當前session沒有包含與之具備相同持久化標識的持久實例,使用update()。若是想隨時合併你的的改動而不考慮session的狀態,使用merge()。換句話說,在一個新session中一般第一個調用的是update()方法,以便保證從新關聯脫管(detached)對象的操做首先被執行。
若是但願相關聯的脫管對象(經過引用「可到達」的脫管對象)的數據也要更新到數據庫時(而且也僅僅在這種狀況),能夠對該相關聯的脫管對象單獨調用update() 固然這些能夠自動完成,即經過使用傳播性持久化(transitive persistence),請看第 10.11 節「傳播性持久化(transitive persistence)」。
lock()方法也容許程序從新關聯某個對象到一個新session上。不過,該脫管(detached)的對象必須是沒有修改過的!
//just reassociate:
sess.lock(fritz, LockMode.NONE);
//do a version check, then reassociate:
sess.lock(izi, LockMode.READ);
//do a version check, using SELECT ... FOR UPDATE, thenreassociate:
sess.lock(pk, LockMode.UPGRADE);
請注意,lock()能夠搭配多種LockMode,更多信息請閱讀API文檔以及關於事務處理(transactionhandling)的章節。從新關聯不是lock()的惟一用途。
其餘用於長時間工做單元的模型會在第 11.3 節「樂觀併發控制(Optimistic concurrency control)」中討論。
Hibernate的用戶曾要求一個既可自動分配新持久化標識(identifier)保存瞬時(transient)對象,又可更新/從新關聯脫管(detached)實例的通用方法。 saveOrUpdate()方法實現了這個功能。
// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catID);
// in a higher tier of the application
Cat mate = new Cat();
cat.setMate(mate);
// later, in a new session
secondSession.saveOrUpdate(cat); // update existing state (cat has a non-nullid)
secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id)
saveOrUpdate()用途和語義可能會使新用戶感到迷惑。首先,只要你沒有嘗試在某個session中使用來自另外一session的實例,你就應該不須要使用update(), saveOrUpdate(),或merge()。有些程序歷來不用這些方法。
一般下面的場景會使用update()或saveOrUpdate():
saveOrUpdate()作下面的事:
merge()可很是不一樣:
使用Session.delete()會把對象的狀態從數據庫中移除。固然,你的應用程序可能仍然持有一個指向已刪除對象的引用。因此,最好這樣理解:delete()的用途是把一個持久實例變成瞬時(transient)實例。
sess.delete(cat);
你能夠用你喜歡的任何順序刪除對象,不用擔憂外鍵約束衝突。固然,若是你搞錯了順序,仍是有可能引起在外鍵字段定義的NOT NULL約束衝突。例如你刪除了父對象,可是忘記刪除孩子們。
偶爾會用到不從新生成持久化標識(identifier),將持久實例以及其關聯的實例持久到不一樣的數據庫中的操做。
//retrieve a cat from one database
Session session1 = factory1.openSession();
Transaction tx1 = session1.beginTransaction();
Cat cat = session1.get(Cat.class, catId);
tx1.commit();
session1.close();
//reconcile with a second database
Session session2 = factory2.openSession();
Transaction tx2 = session2.beginTransaction();
session2.replicate(cat, ReplicationMode.LATEST_VERSION);
tx2.commit();
session2.close();
ReplicationMode決定在和數據庫中已存在記錄由衝突時,replicate()如何處理。
這個功能的用途包括使錄入的數據在不一樣數據庫中一致,產品升級時升級系統配置信息,回滾non-ACID事務中的修改等等。(譯註,non-ACID,非ACID;ACID,Atomic,Consistent,Isolated and Durable的縮寫)
每間隔一段時間,Session會執行一些必需的SQL語句來把內存中的對象的狀態同步到JDBC鏈接中。這個過程被稱爲刷出(flush),默認會在下面的時間點執行:
涉及的SQL語句會按照下面的順序發出執行:
(有一個例外是,若是對象使用native方式來生成ID(持久化標識)的話,它們一執行save就會被插入。)
除非你明確地發出了flush()指令,關於Session什麼時候會執行這些JDBC調用是徹底沒法保證的,只能保證它們執行的先後順序。固然,Hibernate保證,Query.list(..)絕對不會返回已經失效的數據,也不會返回錯誤數據。
也能夠改變默認的設置,來讓刷出(flush)操做發生的不那麼頻繁。 FlushMode類定義了三種不一樣的方式。僅在提交時刷出(僅當Hibernate的Transaction API被使用時有效),按照剛纔說的方式刷出,以及除非明確使用flush()不然從不刷出。最後一種模式對於那些須要長時間保持Session爲打開或者斷線狀態的長時間運行的工做單元頗有用。 (參見 第 11.3.2 節「擴展週期的session和自動版本化」).
sess = sf.openSession();
Transaction tx = sess.beginTransaction();
sess.setFlushMode(FlushMode.COMMIT); // allow queries to returnstale state
Cat izi = (Cat) sess.load(Cat.class, id);
izi.setName(iznizi);
// might return stale data
sess.find("from Cat as cat left outer join cat.kittenskitten");
// change to izi is not flushed!
...
tx.commit(); // flush occurs
sess.close();
刷出(flush)期間,可能會拋出異常。(例如一個DML操做違反了約束)異常處理涉及到對Hibernate事務性行爲的理解,所以咱們將在第 11 章 事務和併發中討論。
10.11. 傳播性持久化(transitive persistence)
對每個對象都要執行保存,刪除或重關聯操做讓人感受有點麻煩,尤爲是在處理許多彼此關聯的對象的時候。一個常見的例子是父子關係。考慮下面的例子:
若是一個父子關係中的子對象是值類型(value typed)(例如,地址或字符串的集合)的,他們的生命週期會依賴於父對象,能夠享受方便的級聯操做(Cascading),不須要額外的動做。父對象被保存時,這些值類型(value typed)子對象也將被保存;父對象被刪除時,子對象也將被刪除。這對將一個子對象從集合中移除是一樣有效:Hibernate會檢測到,而且由於值類型(valuetyped)的對象不可能被其餘對象引用,因此Hibernate會在數據庫中刪除這個子對象。
如今考慮一樣的場景,不過父子對象都是實體(entities)類型,而非值類型(valuetyped)(例如,類別與個體,或母貓和小貓)。實體有本身的生命期,容許共享對其的引用(所以從集合中移除一個實體,不意味着它能夠被刪除),而且實體到其餘關聯實體之間默認沒有級聯操做的設置。 Hibernate默認不實現所謂的可到達即持久化(persistence by reachability)的策略。
每一個Hibernate session的基本操做 - 包括 persist(),merge(), saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate() - 都有對應的級聯風格(cascadestyle)。這些級聯風格(cascadestyle)風格分別命名爲create, merge, save-update, delete,lock, refresh, evict, replicate。若是你但願一個操做被順着關聯關係級聯傳播,你必須在映射文件中指出這一點。例如:
<one-to-one name="person"cascade="persist"/>
級聯風格(cascade style)是可組合的:
<one-to-one name="person"cascade="persist,delete,lock"/>
你能夠使用cascade="all"來指定所有操做都順着關聯關係級聯(cascaded)。默認值是cascade="none",即任何操做都不會被級聯(cascaded)。
注意有一個特殊的級聯風格(cascade style) delete-orphan,只應用於one-to-many關聯,代表delete()操做應該被應用於全部從關聯中刪除的對象。
建議:
能夠使用cascade="all"將一個關聯關係(不管是對值對象的關聯,或者對一個集合的關聯)標記爲父/子關係的關聯。這樣對父對象進行save/update/delete操做就會致使子對象也進行save/update/delete操做。
此外,一個持久的父對象對子對象的淺引用(mere reference)會致使子對象被同步save/update。不過,這個隱喻(metaphor)的說法並不完整。除非關聯是<one-to-many>關聯而且被標記爲cascade="delete-orphan",不然父對象失去對某個子對象的引用不會致使該子對象被自動刪除。父子關係的級聯(cascading)操做準確語義以下:
最後,注意操做的級聯多是在調用期(call time)或者寫入期(flush time)做用到對象圖上的。全部的操做,若是容許,都在操做被執行的時候級聯到可觸及的關聯實體上。然而,save-upate和delete-orphan是在Session flush的時候才做用到全部可觸及的被關聯對象上的。
Hibernate中有一個很是豐富的元級別(meta-level)的模型,含有全部的實體和值類型數據的元數據。有時這個模型對應用程序自己也會很是有用。好比說,應用程序可能在實現一種「智能」的深度拷貝算法時,經過使用Hibernate的元數據來了解哪些對象應該被拷貝(好比,可變的值類型數據),那些不該該(不可變的值類型數據,也許還有某些被關聯的實體)。
Hibernate提供了ClassMetadata接口,CollectionMetadata接口和Type層次體系來訪問元數據。能夠經過SessionFactory獲取元數據接口的實例。
Cat fritz = ......;
ClassMetadata catMeta =sessionfactory.getClassMetadata(Cat.class);
Object[] propertyValues = catMeta.getPropertyValues(fritz);
String[] propertyNames = catMeta.getPropertyNames();
Type[] propertyTypes = catMeta.getPropertyTypes();
// get a Map of all properties which are not collections orassociations
Map namedValues = new HashMap();
for ( int i=0; i<propertyNames.length; i++ ) {
if (!propertyTypes[i].isEntityType() &&!propertyTypes[i].isCollectionType() ) {
namedValues.put(propertyNames[i], propertyValues[i] );
}
}
Hibernate的事務和併發控制很容易掌握。Hibernate直接使用JDBC鏈接和JTA資源,不添加任何附加鎖定行爲。咱們強烈推薦你花點時間瞭解JDBC編程,ANSISQL查詢語言和你使用的數據庫系統的事務隔離規範。
Hibernate不鎖定內存中的對象。你的應用程序會按照你的數據庫事務的隔離級別規定的那樣運做。幸好有了Session,使得Hibernate經過標識符查找,和實體查詢(不是返回標量值的報表查詢)提供了可重複的讀取(Repeatable reads)功能,Session同時也是事務範圍內的緩存(cache)。
除了對自動樂觀併發控制提供版本管理,針對行級悲觀鎖定,Hibernate也提供了輔助的(較小的)API,它使用了SELECT FOR UPDATE的SQL語法。本章後面會討論樂觀併發控制和這個API。
咱們從Configuration層、SessionFactory層, 和 Session層開始討論Hibernate的並行控制、數據庫事務和應用程序的長事務。
11.1. Session和事務範圍(transaction scope)
SessionFactory對象的建立代價很昂貴,它是線程安全的對象,它爲全部的應用程序線程所共享。它只建立一次,一般是在應用程序啓動的時候,由一個Configuraion的實例來建立。
Session對象的建立代價比較小,是非線程安全的,對於單個請求,單個會話、單個的工做單元而言,它只被使用一次,而後就丟棄。只有在須要的時候,一個Session對象纔會獲取一個JDBC的Connection(或一個Datasource)對象,所以倘若不使用的時候它不消費任何資源。
此外咱們還要考慮數據庫事務。數據庫事務應該儘量的短,下降數據庫中的鎖爭用。數據庫長事務會阻止你的應用程序擴展到高的併發負載。所以,倘若在用戶思考期間讓數據庫事務開着,直到整個工做單元完成才關閉這個事務,這毫不是一個好的設計。
一個操做單元(Unit of work)的範圍是多大?單個的Hibernate Session能跨越多個數據庫事務嗎?仍是一個Session的做用範圍對應一個數據庫事務的範圍?應該什麼時候打開 Session,什麼時候關閉Session?,你又如何劃分數據庫事務的邊界呢?
首先,別用session-per-operation這種反模式了,也就是說,在單個線程中,不要由於一次簡單的數據庫調用,就打開和關閉一次Session!數據庫事務也是如此。應用程序中的數據庫調用是按照計劃好的次序,分組爲原子的操做單元。(注意,這也意味着,應用程序中,在單個的SQL語句發送以後,自動事務提交(auto-commit)模式失效了。這種模式專門爲SQL控制檯操做設計的。 Hibernate禁止當即自動事務提交模式,或者指望應用服務器禁止當即自動事務提交模式。)數據庫事務毫不是無關緊要的,任何與數據庫之間的通信都必須在某個事務中進行,無論你是在讀仍是在寫數據。對讀數據而言,應該避免auto-commit行爲,由於不少小的事務比一個清晰定義的工做單元性能差。後者也更容易維護和擴展。
在多用戶的client/server應用程序中,最經常使用的模式是 每一個請求一個會話(session-per-request)。在這種模式下,來自客戶端的請求被髮送到服務器端(即Hibernate持久化層運行的地方),一個新的Hibernate Session被打開,而且執行這個操做單元中全部的數據庫操做。一旦操做完成(同時對客戶端的響應也準備就緒),session被同步,而後關閉。你也能夠使用單個數據庫事務來處理客戶端請求,在你打開Session以後啓動事務,在你關閉Session以前提交事務。會話和請求之間的關係是一對一的關係,這種模式對於大多數應用程序來講是很棒的。
實現纔是真正的挑戰。Hibernate內置了對"當前session(current session)" 的管理,用於簡化此模式。你要作的一切就是在服務器端要處理請求的時候,開啓事務,在響應發送給客戶以前結束事務。你能夠用任何方式來完成這一操做,一般的方案有ServletFilter,在service方法中進行pointcut的AOP攔截器,或者proxy/interception容器。EJB容器是實現橫切諸如EJBsession bean上的事務分界,用CMT對事務進行聲明等方面的標準手段。倘若你決定使用編程式的事務分界,請參考本章後面講到的Hibernate Transaction API,這對易用性和代碼可移植性都有好處。
在任什麼時候間,任何地方,你的應用代碼能夠經過簡單的調用sessionFactory.getCurrentSession()來訪問"當前session",用於處理請求。你老是會獲得當前數據庫事務範圍內的Session。在使用本地資源或JTA環境時,必須配置它,請參見第 2.5 節「上下文相關的(Contextual)Session」。
有時,將Session和數據庫事務的邊界延伸到"展現層被渲染後"會帶來便利。有些serlvet應用程序在對請求進行處理後,有個單獨的渲染期,這種延伸對這種程序特別有用。倘若你實現你本身的攔截器,把事務邊界延伸到展現層渲染結束後很是容易。然而,倘若你依賴有容器管理事務的EJB,這就不太容易了,由於事務會在EJB方法返回後結束,而那是在任何展現層渲染開始以前。請訪問Hibernate網站和論壇,你能夠找到Open Session in View這一模式的提示和示例。
session-per-request模式不只僅是一個能夠用來設計操做單元的有用概念。不少業務處理都需要一系列完整的與用戶之間的交互,而這些用戶是指對數據庫有交叉訪問的用戶。在基於web的應用和企業應用中,跨用戶交互的數據庫事務是沒法接受的。考慮下面的例子:
從用戶的角度來看,咱們把這個操做單元稱爲長時間運行的對話(conversation),或者(or 應用事務,application transaction)。在你的應用程序中,能夠有不少種方法來實現它。
頭一個幼稚的作法是,在用戶思考的過程當中,保持Session和數據庫事務是打開的,保持數據庫鎖定,以阻止併發修改,從而保證數據庫事務隔離級別和原子操做。這種方式固然是一個反模式,由於鎖爭用會致使應用程序沒法擴展併發用戶的數目。
很明顯,咱們必須使用多個數據庫事務來實現這個對話。在這個例子中,維護業務處理的事務隔離變成了應用程序層的部分責任。一個對話一般跨越多個數據庫事務。若是僅僅只有一個數據庫事務(最後的那個事務)保存更新過的數據,而全部其餘事務只是單純的讀取數據(例如在一個跨越多個請求/響應週期的嚮導風格的對話框中),那麼應用程序事務將保證其原子性。這種方式比聽起來還要容易實現,特別是當你使用了Hibernate的下述特性的時候:
session-per-request-with-detached-objects 和 session-per-conversation 各有優缺點,咱們在本章後面樂觀併發控制那部分再進行討論。
11.1.3. 關注對象標識(Considering object identity)
應用程序可能在兩個不一樣的Session中併發訪問同一持久化狀態,可是,一個持久化類的實例沒法在兩個 Session中共享。所以有兩種不一樣的標識語義:
數據庫標識
foo.getId().equals(bar.getId() )
JVM 標識
foo==bar
對於那些關聯到 特定Session (也就是在單個Session的範圍內)上的對象來講,這兩種標識的語義是等價的,與數據庫標識對應的JVM標識是由Hibernate來保證的。不過,當應用程序在兩個不一樣的session中併發訪問具備同一持久化標識的業務對象實例的時候,這個業務對象的兩個實例事實上是不相同的(從 JVM識別來看)。這種衝突能夠經過在同步和提交的時候使用自動版本化和樂觀鎖定方法來解決。
這種方式把關於併發的頭疼問題留給了Hibernate和數據庫;因爲在單個線程內,操做單元中的對象識別不須要代價昂貴的鎖定或其餘意義上的同步,所以它同時能夠提供最好的可伸縮性。只要在單個線程只持有一個 Session,應用程序就不須要同步任何業務對象。在Session 的範圍內,應用程序能夠放心的使用==進行對象比較。
不過,應用程序在Session的外面使用==進行對象比較可能會致使沒法預期的結果。在一些沒法預料的場合,例如,若是你把兩個脫管對象實例放進同一個 Set的時候,就可能發生。這兩個對象實例可能有同一個數據庫標識(也就是說,他們表明了表的同一行數據),從JVM標識的定義上來講,對脫管的對象而言,Hibernate沒法保證他們的的JVM標識一致。開發人員必須覆蓋持久化類的equals()方法和 hashCode() 方法,從而實現自定義的對象相等語義。警告:不要使用數據庫標識來實現對象相等,應該使用業務鍵值,由惟一的,一般不變的屬性組成。當一個瞬時對象被持久化的時候,它的數據庫標識會發生改變。若是一個瞬時對象(一般也包括脫管對象實例)被放入一個Set,改變它的hashcode會致使與這個Set的關係中斷。雖然業務鍵值的屬性不象數據庫主鍵那樣穩定不變,可是你只須要保證在同一個Set 中的對象屬性的穩定性就足夠了。請到Hibernate網站去尋求這個問題更多的詳細的討論。請注意,這不是一個有關Hibernate的問題,而僅僅是一個關於Java對象標識和判等行爲如何實現的問題。
決不要使用反模式session-per-user-session或者 session-per-application(固然,這個規定幾乎沒有例外)。請注意,下述一些問題可能也會出如今咱們推薦的模式中,在你做出某個設計決定以前,請務必理解該模式的應用前提。
數據庫(或者系統)事務的聲明老是必須的。在數據庫事務以外,就沒法和數據庫通信(這可能會讓那些習慣於自動提交事務模式的開發人員感到迷惑)。永遠使用清晰的事務聲明,即便只讀操做也是如此。進行顯式的事務聲明並不老是須要的,這取決於你的事務隔離級別和數據庫的能力,但無論怎麼說,聲明事務總歸有益無害。固然,一個單獨的數據庫事務老是比不少瑣碎的事務性能更好,即時對讀數據而言也是同樣。
一個Hibernate應用程序能夠運行在非託管環境中(也就是獨立運行的應用程序,簡單Web應用程序,或者Swing圖形桌面應用程序),也能夠運行在託管的J2EE環境中。在一個非託管環境中,Hibernate一般本身負責管理數據庫鏈接池。應用程序開發人員必須手工設置事務聲明,換句話說,就是手工啓動,提交,或者回滾數據庫事務。一個託管的環境一般提供了容器管理事務(CMT),例如事務裝配經過可聲明的方式定義在EJB session beans的部署描述符中。可編程式事務聲明再也不須要,即便是 Session 的同步也能夠自動完成。
讓持久層具有可移植性是人們的理想,這種移植髮生在非託管的本地資源環境,與依賴JTA可是使用BMT而非CMT的系統之間。在兩種狀況下你均可以使用編程式的事務管理。Hibernate提供了一套稱爲Transaction的封裝API,用來把你的部署環境中的本地事務管理系統轉換到Hibernate事務上。這個API是可選的,可是咱們強烈推薦你使用,除非你用CMTsession bean。
一般狀況下,結束 Session 包含了四個不一樣的階段:
session的同步(flush,刷出)前面已經討論過了,咱們如今進一步考察在託管和非託管環境下的事務聲明和異常處理。
若是Hibernat持久層運行在一個非託管環境中,數據庫鏈接一般由Hibernate的簡單(即非DataSource)鏈接池機制來處理。session/transaction處理方式以下所示:
//Non-managed environment idiom
Session sess = factory.openSession();
Transaction tx = null;
try {
tx =sess.beginTransaction();
// do some work
...
tx.commit();
}
catch (RuntimeException e) {
if (tx != null)tx.rollback();
throw e; // or displayerror message
}
finally {
sess.close();
}
你不須要顯式flush() Session - 對commit()的調用會自動觸發session的同步(取決於session的第 10.10 節「Session刷出(flush)」)。調用 close() 標誌session的結束。close()方法重要的暗示是,session釋放了JDBC鏈接。這段Java代碼在非託管環境下和JTA環境下均可以運行。
更加靈活的方案是Hibernate內置的"currentsession"上下文管理,前文已經講過:
// Non-managed environment idiom with getCurrentSession()
try {
factory.getCurrentSession().beginTransaction();
// do some work
...
factory.getCurrentSession().getTransaction().commit();
}
catch (RuntimeException e) {
factory.getCurrentSession().getTransaction().rollback();
throw e; // or displayerror message
}
你極可能從未在一個一般的應用程序的業務代碼中見過這樣的代碼片段:致命的(系統)異常應該老是在應用程序「頂層」被捕獲。換句話說,執行Hibernate調用的代碼(在持久層)和處理 RuntimeException異常的代碼(一般只能清理和退出應用程序)應該在不一樣的應用程序邏輯層。Hibernate的當前上下文管理能夠極大地簡化這一設計,你全部的一切就是SessionFactory。異常處理將在本章稍後進行討論。
請注意,你應該選擇 org.hibernate.transaction.JDBCTransactionFactory (這是默認選項),對第二個例子來講,hibernate.current_session_context_class應該是"thread"
若是你的持久層運行在一個應用服務器中(例如,在EJB session beans的後面),Hibernate獲取的每一個數據源鏈接將自動成爲全局JTA事務的一部分。你能夠安裝一個獨立的JTA實現,使用它而不使用EJB。Hibernate提供了兩種策略進行JTA集成。
若是你使用bean管理事務(BMT),能夠經過使用Hibernate的 Transaction API來告訴應用服務器啓動和結束BMT事務。所以,事務管理代碼和在非託管環境下是同樣的。
// BMT idiom
Session sess = factory.openSession();
Transaction tx = null;
try {
tx =sess.beginTransaction();
// do some work
...
tx.commit();
}
catch (RuntimeException e) {
if (tx != null)tx.rollback();
throw e; // or displayerror message
}
finally {
sess.close();
}
若是你但願使用與事務綁定的Session,也就是使用getCurrentSession()來簡化上下文管理,你將不得不直接使用JTA UserTransactionAPI。
// BMT idiom with getCurrentSession()
try {
UserTransaction tx =(UserTransaction)new InitialContext()
.lookup("java:comp/UserTransaction");
tx.begin();
// Do some work onSession bound to transaction
factory.getCurrentSession().load(...);
factory.getCurrentSession().persist(...);
tx.commit();
}
catch (RuntimeException e) {
tx.rollback();
throw e; // or displayerror message
}
在CMT方式下,事務聲明是在sessionbean的部署描述符中,而不須要編程。所以,代碼被簡化爲:
// CMT idiom
Session sess = factory.getCurrentSession();
// do some work
...
在CMT/EJB中甚至會自動rollback,由於倘若有未捕獲的RuntimeException從session bean方法中拋出,這就會通知容器把全局事務回滾。這就意味着,在BMT或者CMT中,你根本就不須要使用Hibernate Transaction API ,你自動獲得了綁定到事務的「當前」Session。
注意,當你配置Hibernate的transactionfactory的時候,在直接使用JTA的時候(BMT),你應該選擇org.hibernate.transaction.JTATransactionFactory,在CMTsession bean中選擇org.hibernate.transaction.CMTTransactionFactory。記得也要設置hibernate.transaction.manager_lookup_class。還有,確認你的hibernate.current_session_context_class未設置(爲了向下兼容),或者設置爲"jta"。
getCurrentSession()在JTA環境中有一個弊端。對after_statement鏈接釋放方式有一個警告,這是被默認使用的。由於JTA規範的一個很愚蠢的限制,Hibernate不可能自動清理任何未關閉的ScrollableResults 或者Iterator,它們是由scroll()或iterate()產生的。你must經過在finally塊中,顯式調用ScrollableResults.close()或者Hibernate.close(Iterator)方法來釋放底層數據庫遊標。(固然,大部分程序徹底能夠很容易的避免在JTA或CMT代碼中出現scroll()或iterate()。)
若是 Session 拋出異常 (包括任何SQLException), 你應該當即回滾數據庫事務,調用 Session.close() ,丟棄該Session實例。Session的某些方法可能會致使session處於不一致的狀態。全部由Hibernate拋出的異常都視爲不能夠恢復的。確保在 finally 代碼塊中調用close()方法,以關閉掉 Session。
HibernateException是一個非檢查期異常(這不一樣於Hibernate老的版本),它封裝了Hibernate持久層可能出現的大多數錯誤。咱們的觀點是,不該該強迫應用程序開發人員在底層捕獲沒法恢復的異常。在大多數軟件系統中,非檢查期異常和致命異常都是在相應方法調用的堆棧的頂層被處理的(也就是說,在軟件上面的邏輯層),而且提供一個錯誤信息給應用軟件的用戶(或者採起其餘某些相應的操做)。請注意,Hibernate也有可能拋出其餘並不屬於 HibernateException的非檢查期異常。這些異常一樣也是沒法恢復的,應該採起某些相應的操做去處理。
在和數據庫進行交互時,Hibernate把捕獲的SQLException封裝爲Hibernate的 JDBCException。事實上,Hibernate嘗試把異常轉換爲更有實際含義的JDBCException異常的子類。底層的SQLException能夠經過JDBCException.getCause()來獲得。Hibernate經過使用關聯到 SessionFactory上的SQLExceptionConverter來把SQLException轉換爲一個對應的JDBCException 異常的子類。默認狀況下,SQLExceptionConverter能夠經過配置dialect 選項指定;此外,也能夠使用用戶自定義的實現類(參考javadocs SQLExceptionConverterFactory類來了解詳情)。標準的 JDBCException子類型是:
EJB這樣的託管環境有一項極爲重要的特性,而它從未在非託管環境中提供過,那就是事務超時。在出現錯誤的事務行爲的時候,超時能夠確保不會無限掛起資源、對用戶沒有交代。在託管(JTA)環境以外,Hibernate沒法徹底提供這一功能。可是,Hiberante至少能夠控制數據訪問,確保數據庫級別的死鎖,和返回巨大結果集的查詢被限定在一個規定的時間內。在託管環境中,Hibernate會把事務超時轉交給JTA。這一功能經過Hibernate Transaction對象進行抽象。
Session sess = factory.openSession();
try {
//set transactiontimeout to 3 seconds
sess.getTransaction().setTimeout(3);
sess.getTransaction().begin();
// do some work
...
sess.getTransaction().commit()
}
catch (RuntimeException e) {
sess.getTransaction().rollback();
throw e; // or displayerror message
}
finally {
sess.close();
}
注意setTimeout()不該該在CMT bean中調用,此時事務超時值應該是被聲明式定義的。
11.3. 樂觀併發控制(Optimistic concurrency control)
惟一可以同時保持高併發和高可伸縮性的方法就是使用帶版本化的樂觀併發控制。版本檢查使用版本號、或者時間戳來檢測更新衝突(而且防止更新丟失)。Hibernate爲使用樂觀併發控制的代碼提供了三種可能的方法,應用程序在編寫這些代碼時,能夠採用它們。咱們已經在前面應用程序對話那部分展現了樂觀併發控制的應用場景,此外,在單個數據庫事務範圍內,版本檢查也提供了防止更新丟失的好處。
11.3.1. 應用程序級別的版本檢查(Application version checking)
未能充分利用Hibernate功能的實現代碼中,每次和數據庫交互都須要一個新的 Session,並且開發人員必須在顯示數據以前從數據庫中重新載入全部的持久化對象實例。這種方式迫使應用程序本身實現版本檢查來確保對話事務的隔離,從數據訪問的角度來講是最低效的。這種使用方式和 entity EJB最類似。
// foo is an instance loaded by a previous Session
session = factory.openSession();
Transaction t = session.beginTransaction();
int oldVersion = foo.getVersion();
session.load( foo, foo.getKey() ); // load the current state
if ( oldVersion!=foo.getVersion ) throw newStaleObjectStateException();
foo.setProperty("bar");
t.commit();
session.close();
version 屬性使用 <version>來映射,若是對象是髒數據,在同步的時候,Hibernate會自動增長版本號。
固然,若是你的應用是在一個低數據併發環境下,並不須要版本檢查的話,你照樣能夠使用這種方式,只不過跳過版本檢查就是了。在這種狀況下,最晚提交生效 (last commit wins)就是你的長對話的默認處理策略。請記住這種策略可能會讓應用軟件的用戶感到困惑,由於他們有可能會碰上更新丟失掉卻沒有出錯信息,或者須要合併更改衝突的狀況。
很明顯,手工進行版本檢查只適合於某些軟件規模很是小的應用場景,對於大多數軟件應用場景來講並不現實。一般狀況下,不只是單個對象實例須要進行版本檢查,整個被修改過的關聯對象圖也都須要進行版本檢查。做爲標準設計範例,Hibernate使用擴展週期的 Session的方式,或者脫管對象實例的方式來提供自動版本檢查。
單個 Session實例和它所關聯的全部持久化對象實例都被用於整個對話,這被稱爲session-per-conversation。Hibernate在同步的時候進行對象實例的版本檢查,若是檢測到併發修改則拋出異常。由開發人員來決定是否須要捕獲和處理這個異常(一般的抉擇是給用戶提供一個合併更改,或者在無髒數據狀況下從新進行業務對話的機會)。
在等待用戶交互的時候, Session 斷開底層的JDBC鏈接。這種方式以數據庫訪問的角度來講是最高效的方式。應用程序不須要關心版本檢查或脫管對象實例的從新關聯,在每一個數據庫事務中,應用程序也不須要載入讀取對象實例。
// foo is an instance loaded earlier by the old session
Transaction t = session.beginTransaction(); // Obtain a new JDBCconnection, start transaction
foo.setProperty("bar");
session.flush(); //Only for last transaction in conversation
t.commit(); //Also return JDBC connection
session.close(); //Only for last transaction in conversation
foo對象知道它是在哪一個Session中被裝入的。在一箇舊session中開啓一個新的數據庫事務,會致使session獲取一個新的鏈接,並恢復session的功能。將數據庫事務提交,使得session從JDBC鏈接斷開,並將此鏈接交還給鏈接池。在從新鏈接以後,要強制對你沒有更新的數據進行一次版本檢查,你能夠對全部可能被其餘事務修改過的對象,使用參數LockMode.READ來調用Session.lock()。你不用lock任何你正在更新的數據。通常你會在擴展的Session上設置FlushMode.NEVER,所以只有最後一個數據庫事務循環纔會真正的吧整個對話中發生的修改發送到數據庫。所以,只有這最後一次數據庫事務纔會包含flush()操做,而後在整個對話結束後,還要close()這個session。
若是在用戶思考的過程當中,Session由於太大了而不能保存,那麼這種模式是有問題的。舉例來講,一個HttpSession應該儘量的小。因爲 Session是一級緩存,而且保持了全部被載入過的對象,所以咱們只應該在那些少許的request/response狀況下使用這種策略。你應該只把一個Session用於單個對話,由於它很快就會出現髒數據。
(注意,早期的Hibernate版本須要明確的對Session進行disconnec和reconnect。這些方法如今已通過時了,打開事務和關閉事務會起到一樣的效果。)
此外,也請注意,你應該讓與數據庫鏈接斷開的Session對持久層保持關閉狀態。換句話說,在三層環境中,使用有狀態的EJB session bean來持有Session,而不要把它傳遞到web層(甚至把它序列化到一個單獨的層),保存在HttpSession中。
擴展session模式,或者被稱爲每次對話一個session(session-per-conversation), 在與自動管理當前session上下文聯用的時候會更困難。你須要提供你本身的CurrentSessionContext實現。請參閱Hibernate Wiki以得到示例。