http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java
 |
 |
內(nèi)容: |
 |
|
 |
相關(guān)內(nèi)容: |
 |
|
 |
訂閱: |
 |
|
| 學(xué)習(xí)映射類層次結(jié)構(gòu)的三個(gè)易于實(shí)現(xiàn)的策略
Xavier Coulon, 電子商務(wù) IT 專家, IBM Business Consulting Services Christian Brousseau, J2EE 顧問
2005 年 1 月 14 日
Hibernate 是一個(gè)對(duì)象關(guān)系映射和持久性框架,它提供了許多高級(jí)特性,從內(nèi)省到多態(tài)和繼承映射。但是,把類的層次結(jié)構(gòu)映射到關(guān)系數(shù)據(jù)庫模型,可能會(huì)比較困難。本文將介紹三個(gè)策略,在日常的編程之中您可以用它們把復(fù)雜的對(duì)象模型容易地映射到關(guān)系數(shù)據(jù)庫模型。
概述 Hibernate 是一個(gè)純 Java 的對(duì)象關(guān)系映射和持久性框架,它允許您用 XML 配置文件把普通 Java 對(duì)象映射到關(guān)系數(shù)據(jù)庫表。使用 Hibernate 能夠節(jié)約大量項(xiàng)目開發(fā)時(shí)間,因?yàn)檎麄€(gè) JDBC 層都由這個(gè)框架管理。這意味著您的應(yīng)用程序的數(shù)據(jù)訪問層位于 Hibernate 之上,完全是從底層數(shù)據(jù)模型中抽象出來的。
比起其他類似的對(duì)象關(guān)系映射技術(shù)(JDO、實(shí)體 bean、內(nèi)部開發(fā)等),Hibernate 有許多優(yōu)勢(shì):它是免費(fèi)的、開源的,已經(jīng)成熟到良好的程度,并得到廣泛應(yīng)用,而且還有一個(gè)非常活躍的社區(qū)論壇。
要把 Hibernate 集成到現(xiàn)有的 Java 項(xiàng)目,則需要執(zhí)行以下步驟:
- 從 Hibernate 的 Web 站點(diǎn)下載 Hibernate 框架的最新發(fā)行版(請(qǐng)參閱 參考資料一節(jié)中的鏈接。)
- 把必需的 Hibernate 庫(JAR 文件)復(fù)制到應(yīng)用程序的 CLASSPATH。
- 創(chuàng)建 XML 配置文件,用它把 Java 對(duì)象映射到數(shù)據(jù)庫表。(我們將在本文中描述這個(gè)過程。)
- 把 XML 配置文件復(fù)制到應(yīng)用程序的 CLASSPATH。
您會(huì)注意到,不必修改任何 Java 對(duì)象,您就可以支持框架。例如,假設(shè)您對(duì) Java 應(yīng)用程序使用的數(shù)據(jù)庫表做了些修改 —— 例如修改了列名。在修改完表之后,您要做的只是更新對(duì)應(yīng)的 XML 配置文件。 您不需要重新編譯任何 Java 代碼。
Hibernate 查詢語言(HQL) Hibernate 提供了一個(gè)查詢語言,叫作 Hibernate 查詢語言(HQL),它與 SQL 很相似。如果您喜歡用老式的 SQL 查詢,那么 Hibernate 也為您提供了使用它們的機(jī)會(huì)。但是我們使用的示例只用 HQL。
HQL 用起來相當(dāng)簡單。您會(huì)發(fā)現(xiàn)所有的關(guān)鍵字都與您熟悉的 SQL 中的關(guān)鍵字類似,例如 SELECT 、 FROM 和 WHERE 。HQL 與 SQL 的差異在于,您不用針對(duì)數(shù)據(jù)模型(即針對(duì)表和列等)直接編寫查詢,而是應(yīng)該針對(duì) Java 對(duì)象,使用 Java 對(duì)象的屬性和關(guān)系編寫查詢。
清單 1 演示了一個(gè)基本的示例。這個(gè) HQL 代碼檢索 firstName 為 “John.” 的所有 Individual 。 清單 1. 基本 HQL 查詢
SELECT * FROM eg.hibernate.mapping.dataobject.Individual WHERE firstName = "John"
|
如果想了解更多有關(guān) HQL 語法的內(nèi)容,那么您可以參閱 Hibernate 的 Web 站點(diǎn)上有關(guān) HQL 的參考材料(請(qǐng)參閱 參考資料,以獲得鏈接)。
XML 配置文件 功能的核心在于 XML 配置文件。這些文件必須存在于應(yīng)用程序的 CLASSPATH 中。我們把它們放在示例代碼包的 config 目錄中(您可以從 參考資料下載)。
我們要研究的第一個(gè)文件是 hibernate.cfg.xml。它包含與數(shù)據(jù)源有關(guān)的信息(數(shù)據(jù)庫 URL、模式名稱、用戶名、口令等),以及對(duì)包含映射信息的其他配置文件的引用。
其余的 XML 文件允許您把 Java 類映射到數(shù)據(jù)庫表。稍后我再深入介紹這些文件,但重要的是要清楚它們的文件名要遵守 ClassName.hbm.xml 這個(gè)模式。
我們的支持示例 在本文中,我們要研究一個(gè)基本示例,演示 Hibernate 如何工作,如何良好地運(yùn)用三個(gè)不同策略,利用 Hibernate 進(jìn)行對(duì)象關(guān)系映射。我們的示例是一家保險(xiǎn)公司使用的應(yīng)用程序,公司必須保持客戶投保的所有產(chǎn)權(quán)的法律記錄。我們隨本文提供了完整的源代碼(請(qǐng)參閱 參考資料);這個(gè)代碼提供了基本功能,您可以根據(jù)它構(gòu)建全功能的應(yīng)用程序,例如 Web 或 Swing 應(yīng)用程序。
我們的示例采用了這類應(yīng)用程序的經(jīng)典用例。用戶提供搜索參數(shù),查找各種類型的客戶(個(gè)人、公司、政府機(jī)構(gòu)等),然后顯示與指定參數(shù)匹配的所有客戶列表 —— 即使這些客戶的類型不同。用戶可以訪問同一列表中的某一特定客戶更加詳細(xì)的視圖。
在我們的應(yīng)用程序中,產(chǎn)權(quán)由 Right 類表示。 Right 可以是 Lease 也可以是 Property 。 Right 由客戶所有。為了表示我們的客戶,我們要使用通用類 Person 。 Person 即可以是 Individual 也可以是 Corporation 。當(dāng)然,保險(xiǎn)公司必須知道這些 Right 被分配給哪個(gè) Estate 。您應(yīng)當(dāng)同意, Estate 這個(gè)術(shù)語代表的意義非常泛。所以,我們要用 Land 和 Building 類給我們的開發(fā)人員提供更具體的操作對(duì)象。
從這個(gè)抽象出發(fā),我們可以開發(fā)圖 1 所示的類模型:
圖 1. 完整的類模型
我們的數(shù)據(jù)庫模型是為了介紹將在本文中討論的三個(gè)不同策略而設(shè)計(jì)的。對(duì)于 Right 層次結(jié)構(gòu)來說,我們要使用一個(gè)表( TB_RIGHT ),并用 DISCRIMINATOR 列映射到正確的類。對(duì)于 Person 結(jié)構(gòu),我們要使用一個(gè)稱為 超表( TB_PERSON )的表,它與另外兩個(gè)表( TB_CORPORATION 和 TB_INDIVIDUAL )共享相同的 ID 。第三個(gè)層次結(jié)構(gòu)( Estate )使用兩個(gè)不同的表( TB_BUILDING 和 TB_LAND ),這兩個(gè)表通過由兩個(gè)列( REF_ESTATE_ID 和 REF_ESTATE_TYPE )組合定義的外鍵連接在一起。
圖 2 顯示了這個(gè)數(shù)據(jù)模型:
圖 2. 完整的數(shù)據(jù)模型
設(shè)置數(shù)據(jù)庫 Hibernate 支持各種各樣的 RDBMS,其中任何一種都可以使用我們的示例。但是,本文的示例代碼和文本已經(jīng)針對(duì) HSQLDB(請(qǐng)參閱 參考資料查找鏈接)進(jìn)行了調(diào)整,這是一個(gè)完全用 Java 語言編寫的全功能的關(guān)系數(shù)據(jù)庫系統(tǒng)。在示例代碼包的 sql 目錄中,可以找到叫作 datamodel.sql 的文件。這個(gè) SQL 腳本可以創(chuàng)建我們示例中使用的數(shù)據(jù)模型。
設(shè)置 Java 項(xiàng)目 雖然您總能用命令行構(gòu)建并執(zhí)行示例代碼,但是您可能想在 IDE 中設(shè)置項(xiàng)目,以便更好地進(jìn)行集成。在示例代碼包里,您可以找到以下目錄:
- config,包含樣本的所有 XML 配置文件(映射、Log4J 等)。
- data,包含 HSQLDB 使用的配置文件。您還可以找到一個(gè)叫作 startHSQLDB.bat 的批處理文件,您可以用它啟動(dòng)數(shù)據(jù)庫。
- src,包含示例的所有源代碼。
請(qǐng)確保把必需的 Java 庫和 XML 配置文件復(fù)制到應(yīng)用程序的 CLASSPATH。只需要 Hibernate 和 HSQLDB 庫,這些代碼就可以正確地編譯和運(yùn)行。您可以從 參考資料一節(jié)下載這些包。
策略 1: 每個(gè)子類一個(gè)表(Persons) 在我們第一個(gè)策略中,我們要看看如何映射我們的 Person 層次結(jié)構(gòu)。您會(huì)注意到,數(shù)據(jù)模型與我們的類模型非常接近。所以,我們要為層次結(jié)構(gòu)中的每個(gè)類采用一個(gè)不同的表,但是所有這些表都必須共享相同的主鍵(我們很快就會(huì)詳細(xì)說明)。Hibernate 在向數(shù)據(jù)庫中插入新記錄時(shí),就會(huì)使用這個(gè)主鍵。在訪問數(shù)據(jù)庫時(shí),它還會(huì)利用同一主鍵執(zhí)行 JOIN 操作。
現(xiàn)在我們需要把對(duì)象層次結(jié)構(gòu)映射到表模型。我們有三個(gè)表( TB_PERSON 、 TB_INDIVIDUAL 、和 TB_CORPORATION )。前面我們提過,它們都有一個(gè)叫作 ID 的列,并將該列作為主鍵。表之間不一定非要有這樣的共享列名稱,但是這是一個(gè)很好的實(shí)踐 —— 這樣做的話,生成的 SQL 查詢更容易閱讀。
在清單 2 所示的 XML 映射文件中,您會(huì)注意到,在 Person 的映射定義中,聲明了兩個(gè)具體的 <joined-subclass> 類。XML 元素 <id> 映射到頂級(jí)表 TB_PERSON 的主鍵,同時(shí) <key> 元素(來自每個(gè)子類)映射到 TB_INDIVIDUAL 的 TB_CORPORATION 表中匹配的主鍵。 清單 2. Person.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="eg.hibernate.mapping.dataobject.Person" table="TB_PERSON" polymorphism="implicit">
<id name="id" column="ID">
<generator class="assigned"/>
</id>
<set name="rights" lazy="false">
<key column="REF_PERSON_ID"/>
<one-to-many class="eg.hibernate.mapping.dataobject.Right" />
</set>
<joined-subclass name="eg.hibernate.mapping.dataobject.Individual" table="TB_INDIVIDUAL">
<key column="id"/>
<property name="firstName" column="FIRST_NAME" type="java.lang.String" />
<property name="lastName" column="LAST_NAME" type="java.lang.String" />
</joined-subclass>
<joined-subclass name="eg.hibernate.mapping.dataobject.Corporation" table="TB_CORPORATION">
<key column="id"/>
<property name="name" column="NAME" type="string" />
<property name="registrationNumber" column="REGISTRATION_NUMBER" type="string" />
</joined-subclass>
</class>
</hibernate-mapping>
|
保存 Individual 的一個(gè)新實(shí)例,形成我們使用 Hibernate 的 Java 代碼非常容易,如清單 3 所示: 清單 3. 保存 Individual 的一個(gè)新實(shí)例
public Object create(Object object) {
Session session = null;
try {
session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
session.save(object);
session.flush();
tx.commit();
...
}
|
接著,Hibernate 生成兩個(gè) SQL INSERT 請(qǐng)求,如清單 4 所示。這兩個(gè)請(qǐng)求面向的是一個(gè) save() 。 清單 4. SQL 插入查詢
insert into TB_PERSON (ID) values (?)
insert into TB_INDIVIDUAL (FIRST_NAME, LAST_NAME, id) values (?, ?, ?)
|
要想訪問數(shù)據(jù)庫中的 Individual ,只需在 HQL 查詢中指定類名即可,如清單 5 所示。 清單 5. 調(diào)用 HQL 查詢
public Person findIndividual(Integer id) {
...
session.find(
"select p from " + Individual.class.getName() + " as p where p.id = ?",
new Object[] { id },
new Type[] { Hibernate.INTEGER });
...
}
|
Hibernate 會(huì)自動(dòng)執(zhí)行 SQL 的 JOIN ,從兩個(gè)表中檢索所有必要信息,如清單 6 所示: 清單 6. 查找 Individual 的 SQL SELECT 查詢
select individual0_.id as ID, individual0_.FIRST_NAME as FIRST_NAME55_,
individual0_.LAST_NAME as LAST_NAME55_
from TB_INDIVIDUAL individual0_
inner join TB_PERSON individual0__1_ on individual0_.id=individual0__1_.ID
where (individual0_.id=? )
|
查詢抽象類 當(dāng)查詢抽象類時(shí),Hibernate 會(huì)自動(dòng)返回一個(gè)集合,由匹配的具體異構(gòu)子類構(gòu)成。例如,如果我們查詢數(shù)據(jù)庫中的每個(gè) Person ,Hibernate 會(huì)返回一列 Individual 和 Corporation 對(duì)象。 |
但是,當(dāng)沒有指定具體類時(shí),Hibernate 需要執(zhí)行 SQL 的 JOIN ,因?yàn)樗恢酪樵兡膫€(gè)表。在 HQL 查詢返回的檢索到的所有表的列中,還會(huì)返回一個(gè)額外的 dynamic 列。Hibernate 使用 clazz 列來初始化和填充返回的對(duì)象。我們把這個(gè)類叫作決定因子(determination dynamic),與我們?cè)诘诙€(gè)策略中使用的方法相對(duì)。
清單 7 顯示了如何指定抽象類的 id 屬性查詢抽象類 Person ,清單 8 顯示了 Hibernate 自動(dòng)生成的 SQL 查詢,其中包括表連接: 清單 7. find() 方法調(diào)用中的 HQL 查詢
public Person find(Integer id) {
...
session.find(
"select p from " + Person.class.getName() + " as p where p.id = ?",
new Object[] { id },
new Type[] { Hibernate.INTEGER });
...
}
| 清單 8. 查找任何類型的 Person 的 SQL SELECT 查詢
select person0_.ID as ID0_,
casewhen(person0__1_.id is not null, 1,
casewhen(person0__2_.id is not null, 2,
casewhen(person0_.ID is not null, 0, -1))) as clazz_0_,
person0__1_.FIRST_NAME as FIRST_NAME61_0_,
person0__1_.LAST_NAME as LAST_NAME61_0_,
person0__2_.NAME as NAME62_0_,
person0__2_.REGISTRATION_NUMBER as REGISTRA3_62_0_
from TB_PERSON person0_
left outer join TB_INDIVIDUAL person0__1_ on person0_.ID=person0__1_.id
left outer join TB_CORPORATION person0__2_ on person0_.ID=person0__2_.id
where person0_.ID=?
|
策略 2:每個(gè)類層次結(jié)構(gòu)一個(gè)表(Rights) 對(duì)于我們的 Right 層次結(jié)構(gòu),我們只使用一個(gè)表( TB_RIGHT )來保存整體類層次結(jié)構(gòu)。您會(huì)注意到, TB_RIGHT 表擁有保存 Right 類層次結(jié)構(gòu)的每個(gè)屬性所需要的所有列。保存的實(shí)例值也就會(huì)保存在表中,每個(gè)沒有使用的列則用 NULL 值填充。(因?yàn)樗教幎际恰岸础保晕覀兘?jīng)常把它叫作 瑞士奶酪表)
在圖 3 中,您會(huì)注意到, TB_RIGHT 表中包含一個(gè)額外的列 DISCRIMINATOR 。Hibernate 用這個(gè)列自動(dòng)初始化對(duì)應(yīng)的類并相應(yīng)進(jìn)行填充。這個(gè)類用映射文件中的 XML 元素 <discriminator> 進(jìn)行映射。
圖 3. TB_RIGHT 表的內(nèi)容
簡明性技巧 在每個(gè)大型項(xiàng)目中,您都會(huì)面臨包含多級(jí)抽象類的復(fù)雜的類層次結(jié)構(gòu)。幸運(yùn)的是,您不必指定抽象類的 discriminator-value ,只需為 Hibernate 實(shí)際要使用的具體類指定這個(gè)值即可。 |
正如清單 2 所示的 Person 映射文件,在清單 9 中,我們映射了抽象類( Right )及其所有屬性。要映射兩個(gè)具體類( Lease 和 Property ),就要使用 <subclass> XML 標(biāo)簽。這個(gè)標(biāo)簽非常簡單;它要求 name 屬性,僅僅是因?yàn)?class 標(biāo)簽要求 discriminator-value 屬性。Hibernate 將用后一個(gè)屬性標(biāo)識(shí)它要處理的類。
從 圖 1 的類圖中您會(huì)注意到, discriminator 不是任何 Java 類都有的屬性。實(shí)際上,它甚至沒有映射。它僅僅是 Hibernate 和數(shù)據(jù)庫之間共享的一個(gè)技術(shù)性的列。 清單 9. Right.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="eg.hibernate.mapping.dataobject.Right" table="TB_RIGHT" polymorphism="implicit">
<id name="id" column="ID">
<generator class="assigned"/>
</id>
<discriminator>
<column name="DISCRIMINATOR"/>
</discriminator>
<property name="date" column="DATE" type="java.sql.Date" />
<many-to-one name="person" class="eg.hibernate.mapping.dataobject.Person" column="REF_PERSON_ID"/>
<any name="estate"
meta-type="string"
id-type="java.lang.Integer">
<meta-value value="LND" class="eg.hibernate.mapping.dataobject.Land"/>
<meta-value value="BLD" class="eg.hibernate.mapping.dataobject.Building"/>
<column name="REF_ESTATE_TYPE"/>
<column name="REF_ESTATE_ID"/>
</any>
<subclass name="eg.hibernate.mapping.dataobject.Property" discriminator-value="PRO"/>
<subclass name="eg.hibernate.mapping.dataobject.Lease" discriminator-value="LEA">
<property name="duration" column="DURATION" type="java.lang.Integer" />
</subclass>
</class>
</hibernate-mapping>
|
在清單 9 的映射文件中,您會(huì)注意到 Right 和 Person 層次結(jié)構(gòu)之間的“多對(duì)一”關(guān)系,它(實(shí)質(zhì)上)與 Person 層次結(jié)構(gòu)(一對(duì)多)的關(guān)系正好相反。還請(qǐng)注意 Right 和 Estate 層次結(jié)構(gòu)之間的關(guān)系;稍后我們將在本文中介紹這層關(guān)系。
使用第一個(gè)策略時(shí),Hibernate 在訪問數(shù)據(jù)庫時(shí)生成了非常有效的 SQL 語句。當(dāng)我們查詢具體類時(shí),如清單 10 所示,會(huì)在 discriminator 的 Hibernate 過濾器上自動(dòng)取值 —— 這是件好事,因?yàn)檫@意味著 Hibernate 只讀取與指定類對(duì)應(yīng)的列。 清單 10. 針對(duì)具體類的 SQL 查詢
select property0_.ID as ID, property0_.DATE as DATE,
property0_.REF_PERSON_ID as REF_PERS4_, property0_.REF_ESTATE_TYPE as REF_ESTA5_,
property0_.REF_ESTATE_ID as REF_ESTA6_
from TB_RIGHT property0_ where property0_.DISCRIMINATOR='PRO'
|
當(dāng)我們查詢抽象類時(shí),事情變得有些復(fù)雜。因?yàn)?Hibernate 不知道您要查詢哪個(gè)特定的類,所以必須讀取每個(gè)列(包括 discriminator 類),然后才能決定要初始化哪個(gè)類,最后再填充它。接下來,discriminator 充當(dāng)?shù)慕巧c第一個(gè)策略中 clazz 列充當(dāng)?shù)慕巧嗤5沁@個(gè)方法顯然更死板,因?yàn)轭惷苯优缮?discriminator 的值。 清單 11. (抽象的) Right 類的 SQL 查詢
select right0_.ID as ID,
right0_.DISCRIMINATOR as DISCRIMI2_,
right0_.DATE as DATE, right0_.REF_PERSON_ID as REF_PERS4_,
right0_.REF_ESTATE_TYPE as REF_ESTA5_, right0_.REF_ESTATE_ID as REF_ESTA6_,
right0_.DURATION as DURATION from TB_RIGHT right0_
|
策略的不兼容 按照 Hibernate 映射的 DTD 定義,本文中描述的前兩個(gè)策略是相互排斥的,這意味著它們無法組合在一起,映射同一個(gè)層次結(jié)構(gòu)。 |
數(shù)據(jù)庫模型的完整性 關(guān)于第二個(gè)策略,有一個(gè)需要重點(diǎn)考慮的地方:為了讓它工作,必須把所有非共享列設(shè)置為 NULLABLE 。因?yàn)殚_發(fā)人員通常會(huì)依賴數(shù)據(jù)庫的約束,所以生成的表可能非常難以處理。(畢竟,把有持續(xù)時(shí)間的 Lease 設(shè)置為 NULL 沒多大意義!)
解決方案之一是用 數(shù)據(jù)庫級(jí)檢測約束。您可以根據(jù) DISCRIMINATOR 的值,定義一套要實(shí)施的規(guī)則,如清單 12 所示。當(dāng)然,數(shù)據(jù)庫引擎必須支持這些特性。而且,由于必須同時(shí)為全部具體類用一個(gè)有效表達(dá)式表示這些約束,所以當(dāng)層次結(jié)構(gòu)發(fā)展的時(shí)候,維護(hù)會(huì)很困難。 清單 12. 數(shù)據(jù)完整性約束
alter table TB_RIGHT
add constraint CHK_RIGHT check(
(discriminant ='DPP' and date is null and duration is null)
or (discriminant ='DLM' and date is not null and duration is not null));
|
策略 3: 每個(gè)具體類一個(gè)表(Estates) 我們的第三個(gè),也是最后一個(gè)策略可能是三個(gè)策略當(dāng)中最有想象力的:每個(gè)具體類一個(gè)表,抽象超類 Estate 沒有表。我們依靠 Hibernate 提供對(duì) 多態(tài)(polymorphism)的支持。在清單 3 所示的 XML 映射文件中,您會(huì)注意到,其中只映射了兩個(gè)具體類( Building 和 Land ): 清單 13. Estate.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="eg.hibernate.mapping.dataobject.Land" table="TB_LAND" polymorphism="implicit">
<id name="id" column="ID">
<generator class="assigned"/>
</id>
<property name="description" column="DESCRIPTION" type="java.lang.String" />
<property name="squareFeet" column="SQUARE_FEET" type="java.lang.Double"/>
</class>
<class name="eg.hibernate.mapping.dataobject.Building" table="TB_BUILDING" polymorphism="implicit">
<id name="id" column="ID">
<generator class="assigned"/>
</id>
<property name="description" column="DESCRIPTION" type="java.lang.String" />
<property name="address" column="ADDRESS" type="java.lang.String"/>
</class>
</hibernate-mapping>
|
在表間共享 ID 值 要點(diǎn)在于,在同一類層次結(jié)構(gòu)中映射的兩個(gè)表之間,不必共享相同的 ID 值。如果您進(jìn)行映射,那么 Hibernate 會(huì)為同一 ID 返回多個(gè)不同的對(duì)象。這可能會(huì)使 Hibernate 弄混 —— 您也會(huì)弄混。 |
當(dāng)您查看清單 13 中的映射文件時(shí),您的第一個(gè)反應(yīng)可能是說:“呵!這個(gè)映射與我每天使用的沒什么不同啊!這里沒什么重要的東西!”而且您這么想應(yīng)當(dāng)是對(duì)的。實(shí)際上,第三個(gè)策略只需要一個(gè)條件:需要把 polymorphism 屬性設(shè)置為 implicit 。
即使在映射文件中找不到 Estate 類,它仍然存在于類層次結(jié)構(gòu)之中。而且,因?yàn)閮蓚€(gè)映射的類( Building 和 Land )是從 Estate 中繼承而來,所以我們可以在 HQL 查詢中使用這個(gè)抽象超類,如清單 14 所示。Hibernate 會(huì)用 內(nèi)省(introspection) 找到擴(kuò)展這個(gè)抽象類的類,以便依次為每個(gè)子類執(zhí)行對(duì)應(yīng)的 SQL 查詢。 清單 14. find() 方法調(diào)用中的 HQL 查詢
public Estate find(Integer id) {
...
List objects =
session.find(
"select e from " + Estate.class.getName() + " as e where e.id = ?",
new Object[] { id },
new Type[] { Hibernate.INTEGER });
...
}
|
為了找到與指定 ID 匹配的 Estate ,Hibernate 必須把清單 15 中的兩個(gè)查詢提交給數(shù)據(jù)庫。 清單 15. SQL 查詢
select land0_.ID as ID, land0_.DESCRIPTION as DESCRIPT2_, land0_.SQUARE_FEET as SQUARE_F3_
from TB_LAND land0_ where (land0_.ID=? )
select building0_.ID as ID, building0_.DESCRIPTION as DESCRIPT2_, building0_.ADDRESS as ADDRESS
from TB_BUILDING building0_ where (building0_.ID=? )
|
正如我們?cè)诘诙€(gè)策略中看到的,在 Right 和 Estate 類之間存在著 多對(duì)一 關(guān)系。在一般的表述中,這兩個(gè)表的關(guān)系可以這么表述:“一個(gè) Estate 可以指向 許多 Right 。但是每個(gè) Right 只能指向 一個(gè) Estate 。”但是從我們數(shù)據(jù)模型的角度來看,我們沒有一個(gè)惟一的表可以用來創(chuàng)建我們的外鍵約束,就像 TB_RIGHT 和 TB_PERSON 之間那樣。這使得我們幾乎不可能創(chuàng)建外鍵。幸運(yùn)的是,Hibernate 為我們提供了一個(gè)非常強(qiáng)大的 XML 映射元素 —— <any> 標(biāo)簽,它的用法如清單 16 所示。 清單 16. any 關(guān)系的 XML 映射
<any name="estate"
meta-type="string"
id-type="java.lang.Integer">
<meta-value value="LND" class="eg.hibernate.mapping.dataobject.Land"/>
<meta-value value="BLD" class="eg.hibernate.mapping.dataobject.Building"/>
<column name="REF_ESTATE_TYPE"/>
<column name="REF_ESTATE_ID"/>
</any>
|
禁止多態(tài) 對(duì)于多態(tài)支持被禁止的類( <class...polymorphism="explicit"...> ),如果針對(duì)它們的超類進(jìn)行查詢,會(huì)把它們排除在外。 |
我們進(jìn)一步查看我們的新映射。我們的 虛擬外鍵 基于 TB_RIGHT 表的兩個(gè)列。第一個(gè)列( REF_ESTATE_TYPE )包含 discriminator 字符串,用這個(gè)字符串映射對(duì)應(yīng)的類名。第二個(gè)( REF_ESTATE_ID )是另外一個(gè)表的主鍵的列名。使用默認(rèn)設(shè)置時(shí),Hibernate 會(huì)在第一個(gè)列中保存映射的類名,這么做可能會(huì)非常消耗空間、沒有效率(特別是在代碼重構(gòu)修改類名的時(shí)候)。謝天謝地,Hibernate 還提供了一個(gè)用 <meta-value> XML 元素把類名映射到字符串約束的方法。這些約束的作用與在第二個(gè)策略中討論的 discriminator 的作用相同。再次聲明,這些特性只包含 Hibernate 和數(shù)據(jù)庫,所以不會(huì)改變類的層次結(jié)構(gòu)。
數(shù)據(jù)庫模型的完整性 雖然標(biāo)準(zhǔn) SQL 不允許對(duì)多個(gè)表同時(shí)針對(duì)指定列進(jìn)行參考約束,但是仍有可能添加觸發(fā)器,根據(jù)讀取的 discriminator 值,觸發(fā)器會(huì)檢測目錄中是否有數(shù)據(jù)。但是,這樣的 完整性實(shí)施 方法可能非常難以維護(hù),也有可能降低數(shù)據(jù)的整體性能。
多態(tài) —— 用到極限! 在使用 Hibernate 內(nèi)置的多態(tài)時(shí)需要記住一件事:如果您一不小心,把所有的類都用 polymorphism 屬性設(shè)置為 implicit ,那么您檢索到的信息可能要比您想要的多得多。清單 17 顯示了一種使用兩個(gè)詞的 HQL 查詢來檢索 整個(gè)數(shù)據(jù)庫 的方法。 清單 17. HQL 查詢
public List all() {
...
List objects = session.find("
from Object");
...
}
|
該查詢的功能非常強(qiáng)大,您覺得呢?當(dāng)然,我們之中沒有多少人需要只用一個(gè) HQL 查詢檢索整個(gè)數(shù)據(jù)庫。這個(gè)(沒有實(shí)際意義)的示例的目的就是為了顯示隱式多態(tài)的能力。您可以利用這個(gè)能力避免把無用的、耗費(fèi)資源的 SQL 查詢發(fā)送到數(shù)據(jù)庫。
結(jié)束語 在本文中,我們?cè)噲D向您提供一個(gè)相當(dāng)簡單的實(shí)現(xiàn)示例,演示 Hibernate 提供的三個(gè)映射策略。回頭來看,每個(gè)策略都有自己的優(yōu)勢(shì)與不足:
- 對(duì)于第一個(gè)策略(每個(gè)子類一個(gè)表),Hibernate 每次初始化和填充對(duì)象時(shí),會(huì)讀取多個(gè)表。如果您的索引定義得很好,而且層次結(jié)構(gòu)不是太深,那么這個(gè)操作可以產(chǎn)生良好的結(jié)果。但是,如果不是這種情況,那么您可能會(huì)遇到各種性能問題。
- 對(duì)于第二個(gè)策略(每個(gè)類層次結(jié)構(gòu)一個(gè)表),您必須用 檢測約束 定義您自己的完整性。但隨著列的數(shù)量與時(shí)俱增,這個(gè)策略可能會(huì)變得難以維護(hù)。另一方面,您可能選擇根本不用這樣的約束,而依靠應(yīng)用程序的代碼來管理自己的數(shù)據(jù)完整性。
- 第三個(gè)策略(每個(gè)具體類一個(gè)表)有一些映射限制,而底層數(shù)據(jù)模型不能使用參照完整性,這意味著您不能發(fā)揮關(guān)系數(shù)據(jù)庫引擎的所有潛力。但是,從好的方面說,該策略很常容易與另兩個(gè)策略組合在一起。
不管您選擇哪種策略,都要記住,在整個(gè)過程當(dāng)中,無需修改 Java 類,這意味著業(yè)務(wù)對(duì)象與持續(xù)性框架之間一點(diǎn)聯(lián)系都沒有。正是這樣高水平的靈活性使 Hibernate 在對(duì)象關(guān)系 Java 項(xiàng)目中如此流行。
參考資料
下載
|
Name |
|
 |
|
Size |
|
 |
|
Download method |
|
 |
|
j-hibernate-source.zip |
|
 |
|
|
|
 |
|
FTP |
|
 |
 |
 |
 |
 |
 |
 |
 |
 |
 |
 |
 |
 |
作者簡介
Xavier Coulon 六年前以 IT 專家的身份加入 IBM 的法國分部,并開始從事各類平臺(tái)上的 ERP 咨詢工作。最近兩年,他一直在處理一個(gè)大型 J2EE 項(xiàng)目,這個(gè)項(xiàng)目包含諸如 Struts 和 Hibernate 之類的開源框架。您可以通過 xavier.coulon@fr.ibm.com 與 Xavier 聯(lián)系。 |
Christian Brousseau 是一個(gè)了不起的加拿大人,他一直在開發(fā)軟件,有十多年的經(jīng)驗(yàn)。最初他做了大量 Windows 開發(fā)(C++、Visual Basic、MFC、ActiveX),后來,從 Java 語言的 1.0 版本起,他開始轉(zhuǎn)移到 Java 項(xiàng)目上。做 J2EE 顧問時(shí)積累的專業(yè)知識(shí)為他提供了一個(gè)去法國的好機(jī)會(huì),在法國,他協(xié)助設(shè)計(jì)、開發(fā)、部署了一些重要的企業(yè)級(jí) J2EE 項(xiàng)目。可以通過 cbrous@fr.ibm.com 與 Christian 聯(lián)系。
|

|