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

|