一個非常簡要的Hibernate體系結(jié)構(gòu)的概要圖:
我們來更詳細(xì)地看一下Hibernate運行時體系結(jié)構(gòu)。由于Hibernate非常靈活,且支持?jǐn)?shù)種應(yīng)用方案, 所以我們這只描述一下兩種極端的情況。“輕型”的體系結(jié)構(gòu)方案,要求應(yīng)用程序提供自己的JDBC 連接并管理自己的事務(wù)。這種方案使用了Hibernate API的最小子集:
- SessionFactory (org.hibernate.SessionFactory)
-
針對單個數(shù)據(jù)庫映射關(guān)系經(jīng)過編譯后的內(nèi)存鏡像,它也是線程安全的(不可變)。 它是生成Session的工廠,本身要用到ConnectionProvider。 該對象可以在進程或集群的級別上,為那些事務(wù)之間可以重用的數(shù)據(jù)提供可選的二級緩存。
- Session (org.hibernate.Session)
-
表示應(yīng)用程序與持久儲存層之間交互操作的一個單線程對象,此對象生存期很短。 其隱藏了JDBC連接,也是Transaction的工廠。 其會持有一個針對持久化對象的必選(第一級)緩存,在遍歷對象圖或者根據(jù)持久化標(biāo)識查找對象時會用到。
- 持久的對象及其集合
-
帶有持久化狀態(tài)的、具有業(yè)務(wù)功能的單線程對象,此對象生存期很短。 這些對象可以是普通的JavaBeans/POJO,唯一特殊的是他們正與(僅僅一個)Session相關(guān)聯(lián)。 這個Session被關(guān)閉的同時,這些對象也會脫離持久化狀態(tài),可以被應(yīng)用程序的任何層自由使用。 (例如,用作跟表示層打交道的數(shù)據(jù)傳輸對象data transfer object。)
- 瞬態(tài)(transient)以及脫管(detached)的對象及其集合
-
持久類的沒有與Session相關(guān)聯(lián)的實例。 他們可能是在被應(yīng)用程序?qū)嵗螅形催M行持久化的對象。 也可能是因為實例化他們的Session已經(jīng)被關(guān)閉而脫離持久化的對象。
- 事務(wù)Transaction (org.hibernate.Transaction)
-
(可選的)應(yīng)用程序用來指定原子操作單元范圍的對象,它是單線程的,生存期很短。 它通過抽象將應(yīng)用從底層具體的JDBC、JTA以及CORBA事務(wù)隔離開。 某些情況下,一個Session之內(nèi)可能包含多個Transaction對象。 盡管是否使用該對象是可選的,但是事務(wù)邊界的開啟與關(guān)閉(無論是使用底層的API還是使用Transaction對象)是必不可少的。
- ConnectionProvider (org.hibernate.connection.ConnectionProvider)
-
(可選的)生成JDBC連接的工廠(同時也起到連接池的作用)。 它通過抽象將應(yīng)用從底層的Datasource或DriverManager隔離開。 僅供開發(fā)者擴展/實現(xiàn)用,并不暴露給應(yīng)用程序使用。
- TransactionFactory (org.hibernate.TransactionFactory)
-
(可選的)生成Transaction對象實例的工廠。 僅供開發(fā)者擴展/實現(xiàn)用,并不暴露給應(yīng)用程序使用。
- 擴展接口
-
Hibernate提供了很多可選的擴展接口,你可以通過實現(xiàn)它們來定制你的持久層的行為。 具體請參考API文檔。
在一個“輕型”的體系結(jié)構(gòu)中,應(yīng)用程序可能繞過 Transaction/TransactionFactory 以及 ConnectionProvider 等API直接跟JTA或JDBC打交道。
一個持久化類的實例可能處于三種不同狀態(tài)中的某一種。 這三種狀態(tài)的定義則與所謂的持久化上下文(persistence context)有關(guān)。 Hibernate的Session對象就是這個所謂的持久化上下文:
- 瞬態(tài)(transient)
-
該實例從未與任何持久化上下文關(guān)聯(lián)過。它沒有持久化標(biāo)識(相當(dāng)于主鍵)。
- 持久(persistent)
-
實例目前與某個持久化上下文有關(guān)聯(lián)。 它擁有持久化標(biāo)識(相當(dāng)于主鍵),并且可能在數(shù)據(jù)庫中有一個對應(yīng)的行。 對于某一個特定的持久化上下文,Hibernate保證持久化標(biāo)識與Java標(biāo)識(其值代表對象在內(nèi)存中的位置)等價。
- 脫管(detached)
-
實例曾經(jīng)與某個持久化上下文發(fā)生過關(guān)聯(lián),不過那個上下文被關(guān)閉了, 或者這個實例是被序列化(serialize)到這個進程來的。 它擁有持久化標(biāo)識,并且在數(shù)據(jù)庫中可能存在一個對應(yīng)的行。 對于脫管狀態(tài)的實例,Hibernate不保證任何持久化標(biāo)識和Java標(biāo)識的關(guān)系。
JMX是管理Java組件(Java components)的J2EE規(guī)范。 Hibernate 可以通過一個JMX標(biāo)準(zhǔn)服務(wù)來管理。 在這個發(fā)行版本中,我們提供了一個MBean接口的實現(xiàn),即 org.hibernate.jmx.HibernateService。
想要看如何在JBoss應(yīng)用服務(wù)器上將Hibernate部署為一個JMX服務(wù)的例子,您可以參考JBoss用戶指南。 我們現(xiàn)在說一下在Jboss應(yīng)用服務(wù)器上,使用JMX來部署Hibernate的好處:
-
Session管理: Hibernate的Session對象的生命周期可以 自動跟一個JTA事務(wù)邊界綁定。這意味著你無需手工開關(guān)Session了, 這項 工作會由JBoss EJB 攔截器來完成。你再也不用擔(dān)心你的代碼中的事務(wù)邊界了(除非你想利用Hibernate提供 的Transaction API來自己寫一個便于移植的的持久層)。 你現(xiàn)在要通過 HibernateContext來操作Session了。
-
HAR 部署: 通常情況下,你會使用JBoss的服務(wù)部署描述符(在EAR或/和SAR文件中)來部署Hibernate JMX服務(wù)。 這種部署方式支持所有常見的Hibernate SessionFactory的配置選項。 不過,你需在部署描述符中,列出你所有的映射文件的名字。如果你使用HAR部署方式, JBoss 會自動探測出你的HAR文件中所有的映射文件。
這些選項更多的描述,請參考JBoss 應(yīng)用程序用戶指南。
將Hibernate以部署為JMX服務(wù)的另一個好處,是可以查看Hibernate的運行時統(tǒng)計信息。參看 第 4.4.6 節(jié) “ Hibernate的統(tǒng)計(statistics)機制 ”.
在應(yīng)用程序中,用來實現(xiàn)業(yè)務(wù)問題實體的(如,在電子商務(wù)應(yīng)用程序中的Customer和Order) 類就是持久化類。不能認(rèn)為所有的持久化類的實例都是持久的狀態(tài)——一個實例的狀態(tài)也可能 是瞬時的或脫管的。
如果這些持久化類遵循一些簡單的規(guī)則,Hibernate能夠工作得最好,這些規(guī)則被稱作, 簡單傳統(tǒng)Java對象(POJO:Plain Old Java Object)編程模型。但是這些規(guī)則沒有一個是必需的。 實際上,Hibernate3對于你的持久化類幾乎不做任何設(shè)想。你可以用其他的方法來表達(dá)領(lǐng)域模型: 比如,使用Map實例的樹型結(jié)構(gòu)。
大多數(shù)Java程序需要用一個持久化類來表示貓科動物。
package eg; import java.util.Set; import java.util.Date; public class Cat { private Long id; // identifier private Date birthdate; private Color color; private char sex; private float weight; private int litterId; private Cat mother; private Set kittens = new HashSet(); private void setId(Long id) { this.id=id; } public Long getId() { return id; } void setBirthdate(Date date) { birthdate = date; } public Date getBirthdate() { return birthdate; } void setWeight(float weight) { this.weight = weight; } public float getWeight() { return weight; } public Color getColor() { return color; } void setColor(Color color) { this.color = color; } void setSex(char sex) { this.sex=sex; } public char getSex() { return sex; } void setLitterId(int id) { this.litterId = id; } public int getLitterId() { return litterId; } void setMother(Cat mother) { this.mother = mother; } public Cat getMother() { return mother; } void setKittens(Set kittens) { this.kittens = kittens; } public Set getKittens() { return kittens; } // addKitten not needed by Hibernate public void addKitten(Cat kitten) { kitten.setMother(this); kitten.setLitterId( kittens.size() ); kittens.add(kitten); } }
這里要遵循四條主要的規(guī)則:
Cat為它的所有持久化字段聲明了訪問方法。很多其他ORM工具直接對 實例變量進行持久化。我們相信從持久化機制中分離這種實現(xiàn)細(xì)節(jié)要好得多。 Hibernate持久化JavaBeans風(fēng)格的屬性,認(rèn)可如下形式的方法名: getFoo, isFoo 和 setFoo。 如果需要,你總是可以切換特定的屬性的指示字段的訪問方法。
屬性不需要要聲明為public的。Hibernate默認(rèn)使用 protected或private的get/set方法對, 對屬性進行持久化。
Cat有一個無參數(shù)的構(gòu)造方法。所有的持久化類都必須有一個 默認(rèn)的構(gòu)造方法(可以不是public的),這樣的話Hibernate就可以使用 Constructor.newInstance()來實例化它們。 我們建議,在Hibernate中,為了運行期代理的生成,構(gòu)造方法至少是 包(package)內(nèi)可見的。
Cat有一個屬性叫做id。這個屬性映射數(shù)據(jù)庫表的主 鍵字段。這個屬性可以叫任何名字,其類型可以是任何的原始類型、原始類型的包裝類型、 java.lang.String 或者是 java.util.Date。 (如果你的老式數(shù)據(jù)庫表有聯(lián)合主鍵,你甚至可以用一個用戶自定義的類,該類擁有這些類型 的屬性。參見后面的關(guān)于聯(lián)合標(biāo)識符的章節(jié)。)
標(biāo)識符屬性是可選的。可以不用管它,讓Hibernate內(nèi)部來追蹤對象的識別。 不推薦使用這個屬性。
實際上,一些功能只對那些聲明了標(biāo)識符屬性的類起作用:
-
托管對象的傳播性重新(和session)關(guān)聯(lián)(級聯(lián)更新或級聯(lián)合并) ——參閱 第 11.11 節(jié) “傳播性持久化(transitive persistence)”
-
Session.saveOrUpdate()
-
Session.merge()
我們建議你對持久化類聲明命名一致的標(biāo)識屬性。我們還建議你使用一 個可以為空(也就是說,不是原始類型)的類型。
代理(proxies)是Hibernate的一個重要的功能,它依賴的條件是,持久 化類或者是非final的,或者是實現(xiàn)了一個所有方法都聲明為public的接口。
你可以用Hibernate持久化一個沒有實現(xiàn)任何接口的final類,但是你 不能使用代理來延遲關(guān)聯(lián)加載,這會限制你進行性能優(yōu)化的選擇。
你也應(yīng)該避免在非final類中聲明 public final的方法。如果你想使用一 個有public final方法的類,你必須通過設(shè)置lazy="false" 來明確的禁用代理。
子類也必須遵守第一條和第二條規(guī)則。它從超類Cat繼承了標(biāo)識屬性。
package eg; public class DomesticCat extends Cat { private String name; public String getName() { return name; } protected void setName(String name) { this.name=name; } }
如果你有如下需求,你必須重載 equals() 和 hashCode()方法:
-
想把持久類的實例放入Set中(當(dāng)表示多值關(guān)聯(lián)時,推薦這么做)
-
想重用脫管實例
Hibernate保證,持久化標(biāo)識(數(shù)據(jù)庫的行)和僅在特定會話范圍內(nèi)的Java標(biāo)識是等值的。因此,一旦 我們混合了從不同會話中獲取的實例,如果我們希望Set有明確的語義,我們必 須實現(xiàn)equals() 和hashCode()。
實現(xiàn)equals()/hashCode()最顯而易見的方法是比較兩個對象 標(biāo)識符的值。如果值相同,則兩個對象對應(yīng)于數(shù)據(jù)庫的同一行,因此它們是相等的(如果都被添加到 Set,則在Set中只有一個元素)。不幸的是,對生成的標(biāo)識不能 使用這種方法。Hibernate僅對那些持久化對象賦標(biāo)識值,一個新創(chuàng)建的實例將不會有任何標(biāo)識值。此外, 如果一個實例沒有被保存(unsaved),并且在一個Set中,保存它將會給這個對象 賦一個標(biāo)識值。如果equals() 和 hashCode()是基于標(biāo)識值 實現(xiàn)的,則其哈希碼將會改變,違反Set的契約。建議去Hibernate的站點看關(guān)于這個 問題的全部討論。注意,這不是一個Hibernate問題,而是一般的Java對象標(biāo)識和相等的語義問題。
我們建議使用業(yè)務(wù)鍵值相等(Business key equality)來實現(xiàn)equals() 和 hashCode()。業(yè)務(wù)鍵值相等的意思是,equals()方法 僅僅比較來自業(yè)務(wù)鍵的屬性,一個業(yè)務(wù)鍵將標(biāo)識在真實世界里(一個天生的候選鍵) 的實例。
public class Cat { ... public boolean equals(Object other) { if (this == other) return true; if ( !(other instanceof 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; } }
注意,業(yè)務(wù)鍵不必是象數(shù)據(jù)庫的主鍵那樣是固定不變的(參見第 12.1.3 節(jié) “關(guān)注對象標(biāo)識(Considering object identity)”)。 對業(yè)務(wù)鍵而言,不可變或唯一的屬性是好的候選。
注意,以下特性在當(dāng)前是基于實驗考慮的,可能會在將來改變。
運行期的持久化實體沒有必要象POJO類或JavaBean對象一樣表示。Hibernate也支持動態(tài)模型 (在運行期使用Map的Map)和象DOM4J的樹模型那 樣的實體表示。使用這種方法,你不用寫持久化類,只寫映射文件就行了。
Hibernate默認(rèn)工作在普通POJO模式。你可以使用配置選項default_entity_mode, 對特定的SessionFactory,設(shè)置一個默認(rèn)的實體表示模式。 (參見表 4.3 “ Hibernate配置屬性 ”。)
下面是用Map來表示的例子。首先,在映射文件中,要聲明 entity-name來代替(或外加)一個類名。
<hibernate-mapping> <class entity-name="Customer"> <id name="id" type="long" column="ID"> <generator class="sequence"/> </id> <property name="name" column="NAME" type="string"/> <property name="address" column="ADDRESS" type="string"/> <many-to-one name="organization" column="ORGANIZATION_ID" class="Organization"/> <bag name="orders" inverse="true" lazy="false" cascade="all"> <key column="CUSTOMER_ID"/> <one-to-many class="Order"/> </bag> </class> </hibernate-mapping>
注意,雖然是用目標(biāo)類名來聲明關(guān)聯(lián)的,但是關(guān)聯(lián)的目標(biāo)類型除了是POJO之外,也可以 是一個動態(tài)的實體。
在使用dynamic-map為SessionFactory 設(shè)置了默認(rèn)的實體模式之后,可以在運行期使用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();
動態(tài)映射的好處是,使原型在不需要實體類實現(xiàn)的情況下,快速轉(zhuǎn)變時間。然而,你無法進行 編譯期的類型檢查,并可能由此會處理很多的運行期異常。幸虧有了Hibernate映射,它使得數(shù) 據(jù)庫的schema能容易的規(guī)格化和合理化,并允許稍后添加正確的領(lǐng)域模型的最新實現(xiàn)。
實體表示模式也能在每個Session的基礎(chǔ)上設(shè)置:
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調(diào)用getSession()是在 Session的API中,而不是SessionFactory。 這樣,新的Session共享底層的JDBC連接,事務(wù),和其他的上下文信 息。這意味著,你不需要在第二個Session中調(diào)用 flush()和close(),同樣的,把事務(wù)和連接的處理 交給原來的工作單元。
關(guān)于XML表示能力的更多信息可以在第 19 章 XML映射中找到。
TODO:在property和proxy的包里,用戶擴展文件框架。