引言
讓我們面對現(xiàn)實吧,如果您在企業(yè)應(yīng)用程序中以手工方式編寫 SQL 語句的代碼,那么您將花費(fèi)大量的開發(fā)時間去更新和維護(hù)持久性層。要是能夠方便地將現(xiàn)有 Java? 對象持久保存到關(guān)系數(shù)據(jù)庫(如 IBM? DB2? Universal Database?,UDB)豈不是很好?
幸運(yùn)的是存在這樣的辦法。對象/關(guān)系(Object/Relational,O/R)映射工具是一些成熟的工具,它們能夠?qū)ο笥成涞疥P(guān)系數(shù)據(jù)庫中的行,從而不再需要復(fù)雜的持久層,并且使開發(fā)人員只需編寫最少的 SQL,在多數(shù)情況下不需編寫任何 SQL。
Hibernate 是按照 LGPL 許可證發(fā)布的開放式源代碼應(yīng)用程序,它是“用于 Java 的超高性能的對象/關(guān)系持久性和查詢服務(wù)”。在本文中,我們將為您說明如何使用 Hibernate 方便地(一行 SQL 代碼都不用寫)將 Java 對象持久保存到 DB2 數(shù)據(jù)庫中。
為了演示 Hibernate 的工作機(jī)制,我們將創(chuàng)建一個簡單的類模型,它由兩個類組成:Employee 和 Department。為了簡單起見,一名員工(employee)有一個部門(department),而部門沒有到員工的引用。有關(guān)類圖參閱圖 1。
圖 1. Employee/Department 類圖

我們將使用 WebSphere? Studio 5.0 的 Application Developer 配置和一個稱為 Hibernator 的插件來開發(fā)應(yīng)用程序,Hibernator 能簡化一些配置 Hibernate 的工作。
設(shè)置 WebSphere Studio 和 Java 項目
首先,讓我們花些時間準(zhǔn)備實驗所需的要素:
在 WebSphere Studio 中創(chuàng)建一個新的 Java 項目。
從 http://sourceforge.net/projects/hibernate/ 下載 Hibernate。在撰寫本文時,Hibernate 的版本是 1.2.4。
解壓縮從 SourceForge 得到的 Hibernate 壓縮文檔,將其中的內(nèi)容解壓縮到一個臨時目錄。
將名為 hibernate.jar 的 JAR 文件導(dǎo)入到項目的基本目錄。
從 Hibernate 分發(fā)包中的 lib 目錄導(dǎo)入以下 jar 文件:
commons-lang.jar
commons-collections.jar
commons-logging.jar
xml-apis.jar
xerces.jar
將 hibernate.jar 添加到您的 Java 構(gòu)建路徑(Java Build Path)(用鼠標(biāo)右鍵單擊 project -> Properties -> Java Build Path -> Libraries -> Add JARs...,然后指向您所導(dǎo)入的 hibernate.jar)。
從 SourceForge(http://sourceforge.net/projects/hibernator/)下載 Hibernate Eclipse 插件。您會看到,這個插件使得同步現(xiàn)有的 Java 類和定義我們的 O-R 映射規(guī)則的 Hibernate 映射文件更容易。在撰寫本文時,該插件的版本是 0.9.3。
將這個插件的壓縮文件解壓縮到 [WSAD 5 InstallDir]\eclipse\plugins\ 目錄。
要與 DB2 UDB 進(jìn)行交互,我們還需要導(dǎo)入 DB2 JDBC 數(shù)據(jù)庫驅(qū)動程序。導(dǎo)入缺省情況下位于 C:\program files\IBM\SQLLIB\java\ 目錄的 db2java.zip 文件。確保將 db2java.zip 添加到類路徑中。
我們已經(jīng)在本文所附帶的代碼中包含了一些 JUnit 測試。如果要運(yùn)行這些測試,需要導(dǎo)入缺省情況下位于 [WSAD5InstallDir]\eclipse\plugins\org.junit_3.7.0 目錄的 junit.jar 文件。
我們必須重新啟動 WebSphere Studio,以便它注冊我們所添加的插件。
配置 hibernate.properties
為了促進(jìn)與 DB2 UDB 的通信,我們需要讓 Hibernate 知道一些我們的數(shù)據(jù)庫屬性。為此,我們將創(chuàng)建一個名為 hibernate.properties 的文件,這個文件必須出現(xiàn)在我們應(yīng)用程序的類路徑中。在我們的示例中,我們將把這個屬性文件放到項目的基本目錄中,這個目錄包含在類路徑中。您可能需要針對您自己的數(shù)據(jù)庫設(shè)置更改下列屬性值。
hibernate.connection.driver_class = COM.ibm.db2.jdbc.app.DB2Driver
hibernate.connection.url = jdbc:db2:empl
hibernate.connection.username = db2admin
hibernate.connection.password = db2admin
hibernate.dialect = cirrus.hibernate.sql.DB2Dialect
如果您曾經(jīng)不得已編寫過檢索 JDBC 連接的代碼,那么前四個參數(shù)對您來說應(yīng)該是很熟悉的。hibernate.dialect 屬性告訴 hibetnate 我們在使用 DB2“方言”(dialect)。設(shè)置這個“方言”允許 Hibernate 在缺省情況下啟用一些特定于 DB2 的功能,這樣您就不用手工設(shè)置它們了。
創(chuàng)建數(shù)據(jù)庫模式
上面的屬性文件引用了我們還未創(chuàng)建的名為 empl 的數(shù)據(jù)庫。讓我們繼續(xù)向前,完成這項工作。讓我們轉(zhuǎn)到 DB2 命令行處理器:
db2=> create db empl
db2=> connect to empl user db2admin using db2admin
此外,我們需要用到一些表:
db2=> create table Employee (
EID int NOT NULL PRIMARY KEY,
FirstName varchar(30) NOT NULL,
LastName varchar(30) NOT NULL,
Email varchar(30) NOT NULL,
ManagerEID int, DepartmentID int NOT NULL)
db2=> create table Department(
DepartmentID int NOT NULL PRIMARY KEY,
Name varchar(30) NOT NULL,
City varchar(30) NOT NULL,
State varchar(30) NOT NULL)
創(chuàng)建要被映射的 JavaBeans
回想一下,您閱讀本文目的正是要將 Java 對象映射到數(shù)據(jù)庫。那我們就來定義這些對象:
創(chuàng)建新的類 Department:
package com.ibm.hibernate_article;
public class Department
{
private int departmentID;
private String name;
private String city;
private String state;}
創(chuàng)建新的類 Employee:
package com.ibm.hibernate_article;
public class Employee
{
private int employeeId;
private String firstName;
private String lastName;
private String email;
private Employee manager;
private Department department;}
對于我們新創(chuàng)建的兩個類:
在大綱視圖中,用鼠標(biāo)右鍵單擊類名。
選擇 Generate Getter and Setter...。
選擇 All。
單擊 OK。
請記住,所有的 setter 和 getter 都必須存在,不過它們的可視性無關(guān)緊要。這樣,如果您需要維護(hù)不變的對象,那么您可以在構(gòu)造該對象期間設(shè)置其狀態(tài),并且將所有 setter 方法設(shè)為私有。除了所創(chuàng)建的任何其它構(gòu)造器之外,您還必須提供一個缺省構(gòu)造器;不過,缺省構(gòu)造器的可視性也可以設(shè)為私有。setter 和 getter 方法以及缺省構(gòu)造器之所以必須存在,是因為 Hibernate 遵循 JavaBeans 語法并且使用這些方法特征符來在 O/R 映射期間持久保持?jǐn)?shù)據(jù)。
創(chuàng)建 XML 映射
既然 Java 類和數(shù)據(jù)庫表已經(jīng)準(zhǔn)備就緒,現(xiàn)在我們需要定義 O/R 映射。Hibernate 通過讀取包含映射定義的 XML 文件來實現(xiàn)這個目標(biāo)。
讓我們首先為 Employee 類創(chuàng)建映射。
在編輯器中打開 Employee.java 文件。
單擊 Window -> Show View -> Other -> Hibernator -> Hibernator(請參閱圖 2)。
圖 2. 顯示 Hibernator 視圖

在 Hibernator 視圖中用鼠標(biāo)右鍵單擊,然后單擊 Save(圖 3)。
圖 3. 用 Hibernator 插件生成 O/R 映射 XML 文件

我們還必須做一些編輯工作,但該視圖實際上沒有提供編輯功能,它只是生成 .hbm.xml 文件。這樣,我們將需要在常規(guī)的文件編輯器中打開 Employee.hbm.xml 文件。
分析映射文件
該插件生成了一個文件,內(nèi)容如下:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"
<hibernate-mapping>
<class name="com.ibm.hibernate_article.Employee" table="employee">
<many-to-one name="department"/>
<property name="email"/>
<property name="employeeId"/>
<property name="firstName"/>
<property name="lastName"/>
<many-to-one name="manager"/>
</class>
</hibernate-mapping>
<!-- parsed in 0ms -->
您會注意到,在 DOCTYPE 中定義的文檔類型定義(document type definition,DTD)文件存在于指定的 URL(即 http://hibernate.sourceforge.net/hibernate-mapping.dtd)。如果該映射文件存在于類路徑中,那么 Hibernate 將總是首先從那里引用它。因為該映射文件在 hibernate.jar 文件中,所以將會在您的類路徑中,因此,您將不必?fù)?dān)心需要手工將它導(dǎo)入。這個文件用來定義 XML 文件中允許的有效標(biāo)記。
<hibernate-mapping> 標(biāo)記是這個 XML 文件中的基本標(biāo)記。這個標(biāo)記有兩個可選的屬性,但我們的應(yīng)用程序不需要它們。請參閱 Hibernate 文檔,了解關(guān)于這些特征的更多信息。
<class> 元素代表一個持久的 Java 類。它有一個 name(名稱)屬性,這個屬性引用我們正在映射的 Java 類的全限定(用點(diǎn)隔開)類名。它還有一個 table(表)屬性,這個屬性引用我們的類所映射到的數(shù)據(jù)庫表(即員工表)。該插件沒有為我們生成 table 屬性,所以我們將在下一節(jié)中添加它。
<class> 元素還必須包含一個 <id> 元素,用來指定哪個字段是該數(shù)據(jù)庫表的主鍵,并指定如何生成該主鍵。我們同樣將在下一節(jié)中討論這個問題。
Hibernate 文檔提到:“<property> 元素聲明了持久的類的 JavaBean 樣式屬性”。該屬性主要用于基本類型或字符串類型的實例變量。在我們的示例中,員工的名字必須用一個 <property> 元素來代表。
many-to-one(多對一)元素用于“與另一個持久類的普通關(guān)聯(lián)……。關(guān)系模型是一種多對一(many-to-one)關(guān)聯(lián)。(它實際上就是一個對象引用。)”在我們的示例中,Employee 類與 Department 類之間存在 many-to-one 關(guān)聯(lián)。
修改映射文件
我們的 employeeId 實例變量將映射到數(shù)據(jù)庫中的 EID 列。因為 EID 將成為我們的主鍵,所以我們需要除去為 employeeId 生成的 property 元素,并且用 id 元素替代它:
<id name=" employeeId " column="EID">
<generator class="assigned"/>
</id>
<id> 元素的 name 屬性引用我們的類中的 JavaBean 參數(shù)的名稱。column 屬性引用數(shù)據(jù)庫中我們映射到的列。generator 元素的 class 屬性被設(shè)置成“assigned”,意思是我們打算自己指定對象中主鍵的值。對于自動生成主鍵,還有其它一些 Hibernate 選項可供選擇。您可以在 Hibernate 文檔中找到關(guān)于這些選項的更多信息。
現(xiàn)在,我們需要進(jìn)一步修改該插件所生成的代碼,并著手定義每一個 <property> 和 <many-to-one> 標(biāo)記將映射到哪些列:
<property name="email" column="Email"/>
<property name="firstName" column="FirstName"/>
<property name="lastName" column="LastName"/>
<many-to-one name="department" column="departmentID"/>
<many-to-one name="manager" column="managerEID"/>
這樣,修改后的文檔內(nèi)容如下:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"
<hibernate-mapping>
<class name="com.ibm.hibernate_article.Employee" table="employee">
<id name="employeeId" column="EID">
<generator class="assigned"/>
</id>
<property name="email" column="Email"/>
<property name="firstName" column="FirstName"/>
<property name="lastName" column="LastName"/>
<many-to-one name="department" column="departmentID"/>
<many-to-one name="manager" column="managerEID"/>
</class>
</hibernate-mapping>
我們將對 Department.hbm.xml 做本質(zhì)上一樣的處理。最后得到的結(jié)果如下:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"
<hibernate-mapping>
<class name="com.ibm.hibernate_article.Department" table="department">
<id name="departmentID" column="DepartmentID">
<generator class="assigned"/>
</id>
<property name="city" column="City"/>
<property name="name" column="Name"/>
<property name="state" column="State"/>
</class>
</hibernate-mapping>
創(chuàng)建數(shù)據(jù)源和會話
我們需要將 XML 映射裝入到某種對象表示中,這樣 Hibernate 才可以使用它們。具體做法是創(chuàng)建 cirrus.hibernate.Datastore 類的一個實例。然后我們告訴這個 Datastore 實例為給定的類存儲映射信息,辦法是調(diào)用 storeClass 方法并給這個方法提供給定類的 Class 對象。storeClass 方法知道使用全限定類名在同一個包內(nèi)查找相應(yīng)的 .hbm.xml 映射文件。
在擁有 Datastore 對象之后,我們需要用它來構(gòu)建 SessionFactory。這個 SessionFactory 將負(fù)責(zé)創(chuàng)建一些 Session 對象。Hibernate 文檔將會話定義為“單線程的、存在時間短的對象,代表應(yīng)用程序與持久存儲之間的對話”。會話包裝 JDBC 連接,充當(dāng) Transaction 對象的工廠,并管理應(yīng)用程序中的持久對象。會話可以跨越多個事務(wù),所以它不必像事務(wù)那樣代表一個工作原子單元。
讓我們創(chuàng)建一個靜態(tài)的初始化程序,它將負(fù)責(zé)創(chuàng)建 SessionFactory 對象。當(dāng)?shù)谝淮我迷擃悤r,這個靜態(tài)的初始化程序?qū)⒀b入一次。在靜態(tài)地裝入這個初始化程序之后,我們將不再需要重新裝入 Employee 和 Department 類映射。
private SessionFactory sessionFactory;
static {
try
{
Datastore ds = Hibernate.createDatastore();
ds.storeClass(Employee.class);
ds.storeClass(Department.class);
sessionFactory = ds.buildSessionFactory();
}
catch (Exception e)
{
throw new RuntimeException("couldn''t get connection");
}
}
在上述代碼中,Datastore 對象通過調(diào)用 buildSessionFactory 方法獲取 SessionFactory 的一個實例。如果沒有給 buildSessionFactory 方法提供任何參數(shù),它會在運(yùn)行時類路徑中查找缺省屬性文件(即我們前面創(chuàng)建的 hibernate.properties 文件)查找。另一種辦法是,如果在代碼中需要對 properties 進(jìn)行這種控制,可以將 Properties 對象傳遞給 buildSessionFactory 方法。
在該靜態(tài)的初始化程序初始化 SessionFactory 對象之后,我們就可以調(diào)用 openSession() 方法。這個靜態(tài)方法將為我們返回一個新的會話對象。如果您調(diào)用 openSession 時沒有提供參數(shù),SessionFactory 將為您自動管理 Connection。許多連接參數(shù)(如池的大小、語句高速緩存以及空閑時間)都可以通過 hibernate.properties 文件中的參數(shù)(或提供給 SessionFactory 的 properties 對象)進(jìn)行配置。有關(guān)更多詳細(xì)信息,請參閱 Hibernate 文檔。
如果您的程序已經(jīng)有一個現(xiàn)有的連接管理基礎(chǔ)結(jié)構(gòu),那么您可以給 openSession(Connection con) 方法提供一個連接,Hibernate 將使用您提供的連接。
操作數(shù)據(jù)庫對象
這一節(jié)描述如何寫到數(shù)據(jù)庫中,如何從數(shù)據(jù)庫裝入對象以及如何更新和查詢數(shù)據(jù)庫。
寫到數(shù)據(jù)庫
要寫到數(shù)據(jù)庫,我們將使用 SessionFactory 對象打開一個新的會話。然后,我們將創(chuàng)建想持久保存的對象并將它保存到會話中。接著,我們刷新(flush)會話,在連接上調(diào)用提交(commit),最后關(guān)閉(close)會話。
刷新會話會強(qiáng)制 Hibernate 把內(nèi)存中的數(shù)據(jù)和數(shù)據(jù)庫同步起來。Hibernate 將定期自動刷新,但不能保證在什么時候進(jìn)行。于是,我們將內(nèi)存中的數(shù)據(jù)顯式刷新到數(shù)據(jù)庫,從而確保數(shù)據(jù)立即寫入數(shù)據(jù)庫。
在關(guān)閉會話之前,還必須確保提交了數(shù)據(jù)庫連接。
Session session = sessionFactory.openSession();
department = new Department();
department.setCity("Austin");
department.setState("TX");
department.setName("IBM Global Services");
department.setDepartmentID(211);
session.save(department);
session.flush();
session.connection().commit();
session.close();
從數(shù)據(jù)庫裝入對象
裝入對象就是使用對象的標(biāo)識將對象調(diào)回到內(nèi)存中的過程。這與我們在查詢數(shù)據(jù)庫中討論的查詢對象不同。
為了從數(shù)據(jù)庫裝入對象,我們同樣需要一個會話。我們還需要想裝入的對象的主鍵。就我們前面所編寫的示例來說,如果想將 Department 裝回到對象中,我們可以用表示 Department 的 Class 對象來調(diào)用 session.load 方法,我們的主鍵是“211”。
Session session = sessionFactory.openSession();
Department dept = (Department) session.load(Department.class, new Integer(211));
session.close();
更新數(shù)據(jù)庫
要更新一個對象,可以在創(chuàng)建該對象的那個會話中進(jìn)行,也可以在一個完全不同的會話中進(jìn)行。在同一個會話中更新對象很容易;只要修改對象的狀態(tài)就行了。要在不同的會話中更新對象,則必須裝入(或查詢)該對象,然后更新它。
同一個會話
session.save(department);
session.flush();
department.setName("newName");
session.flush();
不同的會話
//first session
Department department = new Department();
department.setDepartmentId(211);
department.setName("someName");
.
. // set other stuff on department
.
session.save(department);
session.flush();
session.connection().commit();
session.close();
//later session
laterSession = sessionFactory.openSession();
Department dept = (Department) session.load(Department.class, new Integer(211));
dept.setName("aDifferentName");
laterSession.flush();
session.connection().commit();
laterSession.close();
查詢數(shù)據(jù)庫
查詢數(shù)據(jù)庫有幾種方式。最簡單的方式是使用 session.find 方法。您必須使用 Hibernate 簡單但卻功能強(qiáng)大的面向?qū)ο蟮牟樵冋Z言來給 session.find 提供一個查詢。下面的示例演示了一個非常簡單的查詢。如果要進(jìn)行更復(fù)雜的查詢,請參閱 Hibernate 文檔獲取更多詳細(xì)信息。
Department department = new Department();
department.setDepartmentId(211);
department.setName("someName");
.
. // set other stuff on department
.
session.save(department);
List list = session.find
("from dept in class com.ibm.hibernate_article.Department where dept.city=''Austin'' ");
進(jìn)行測試
既然我們有了會話對象并且知道了如何進(jìn)行一些操作,那我們就可以在我們簡單的對象模型上編寫一些 CRUD 測試來看看 Hibernate 的實際運(yùn)行情況。這是一種比 JUnit 測試好得多的測試方法。您可以隨意看看本文所附的源代碼中的 HibernateTest.java 測試用例。
結(jié)束語
在本文中,我們僅僅粗淺地討論了如何使用 Hibernate。我們在幾個 POJO(Plain Old Java Object,傳統(tǒng)的 Java 對象)的上下文中介紹 Hibernate API。不過,請注意廣泛的 Hibernate API 涵蓋了更高級的主題,如 one-to-many(一對多)映射、事務(wù)以及集合。既然您已“涉足”Hibernate,那應(yīng)該能更自在地探索如何在編程工作中使用開放式源代碼產(chǎn)品了吧。
我們在一些項目中使用了 Hibernate,使用時遇到了一些障礙。我們發(fā)現(xiàn),SourceForge 上的 Hibernate 論壇對于解答我們的問題是不可或缺的。在該論壇上,Hibernate 的主要開發(fā)人員非常活躍,他們幾乎會解答所有貼子。
論壇的網(wǎng)址為:
http://sourceforge.net/forum/forum.php?forum_id=128638
Hibernate Web 頁面的網(wǎng)址:
http://hibernate.bluemars.net/
關(guān)于作者
Javid Jamae 是一位專攻企業(yè)應(yīng)用程序和軟件方法學(xué)咨詢的獨(dú)立軟件顧問。您可以通過 javidjamae@yahoo.com 與 Javid 聯(lián)系。
Kulvir Singh Bhogal 的工作角色是 WebSphere 顧問,在全美實施 IBM 的電子商務(wù)戰(zhàn)略。您可以通過 kbhogal@us.ibm.com 與 Kulvir 聯(lián)系。