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

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

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

我們還必須做一些編輯工作,但該視圖實際上沒有提供編輯功能,它只是生成 .hbm.xml 文件。這樣,我們將需要在常規的文件編輯器中打開 Employee.hbm.xml 文件。
分析映射文件
該插件生成了一個文件,內容如下:
<?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 文件中,所以將會在您的類路徑中,因此,您將不必擔心需要手工將它導入。這個文件用來定義 XML 文件中允許的有效標記。
<hibernate-mapping> 標記是這個 XML 文件中的基本標記。這個標記有兩個可選的屬性,但我們的應用程序不需要它們。請參閱 Hibernate 文檔,了解關于這些特征的更多信息。
<class> 元素代表一個持久的 Java 類。它有一個 name(名稱)屬性,這個屬性引用我們正在映射的 Java 類的全限定(用點隔開)類名。它還有一個 table(表)屬性,這個屬性引用我們的類所映射到的數據庫表(即員工表)。該插件沒有為我們生成 table 屬性,所以我們將在下一節中添加它。
<class> 元素還必須包含一個 <id> 元素,用來指定哪個字段是該數據庫表的主鍵,并指定如何生成該主鍵。我們同樣將在下一節中討論這個問題。
Hibernate 文檔提到:“<property> 元素聲明了持久的類的 JavaBean 樣式屬性”。該屬性主要用于基本類型或字符串類型的實例變量。在我們的示例中,員工的名字必須用一個 <property> 元素來代表。
many-to-one(多對一)元素用于“與另一個持久類的普通關聯……。關系模型是一種多對一(many-to-one)關聯。(它實際上就是一個對象引用。)”在我們的示例中,Employee 類與 Department 類之間存在 many-to-one 關聯。
修改映射文件
我們的 employeeId 實例變量將映射到數據庫中的 EID 列。因為 EID 將成為我們的主鍵,所以我們需要除去為 employeeId 生成的 property 元素,并且用 id 元素替代它:
<id name=" employeeId " column="EID">
<generator class="assigned"/>
</id>
<id> 元素的 name 屬性引用我們的類中的 JavaBean 參數的名稱。column 屬性引用數據庫中我們映射到的列。generator 元素的 class 屬性被設置成“assigned”,意思是我們打算自己指定對象中主鍵的值。對于自動生成主鍵,還有其它一些 Hibernate 選項可供選擇。您可以在 Hibernate 文檔中找到關于這些選項的更多信息。
現在,我們需要進一步修改該插件所生成的代碼,并著手定義每一個 <property> 和 <many-to-one> 標記將映射到哪些列:
<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"/>
這樣,修改后的文檔內容如下:
<?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 做本質上一樣的處理。最后得到的結果如下:
<?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>
創建數據源和會話
我們需要將 XML 映射裝入到某種對象表示中,這樣 Hibernate 才可以使用它們。具體做法是創建 cirrus.hibernate.Datastore 類的一個實例。然后我們告訴這個 Datastore 實例為給定的類存儲映射信息,辦法是調用 storeClass 方法并給這個方法提供給定類的 Class 對象。storeClass 方法知道使用全限定類名在同一個包內查找相應的 .hbm.xml 映射文件。
在擁有 Datastore 對象之后,我們需要用它來構建 SessionFactory。這個 SessionFactory 將負責創建一些 Session 對象。Hibernate 文檔將會話定義為“單線程的、存在時間短的對象,代表應用程序與持久存儲之間的對話”。會話包裝 JDBC 連接,充當 Transaction 對象的工廠,并管理應用程序中的持久對象。會話可以跨越多個事務,所以它不必像事務那樣代表一個工作原子單元。
讓我們創建一個靜態的初始化程序,它將負責創建 SessionFactory 對象。當第一次引用該類時,這個靜態的初始化程序將裝入一次。在靜態地裝入這個初始化程序之后,我們將不再需要重新裝入 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 對象通過調用 buildSessionFactory 方法獲取 SessionFactory 的一個實例。如果沒有給 buildSessionFactory 方法提供任何參數,它會在運行時類路徑中查找缺省屬性文件(即我們前面創建的 hibernate.properties 文件)查找。另一種辦法是,如果在代碼中需要對 properties 進行這種控制,可以將 Properties 對象傳遞給 buildSessionFactory 方法。
在該靜態的初始化程序初始化 SessionFactory 對象之后,我們就可以調用 openSession() 方法。這個靜態方法將為我們返回一個新的會話對象。如果您調用 openSession 時沒有提供參數,SessionFactory 將為您自動管理 Connection。許多連接參數(如池的大小、語句高速緩存以及空閑時間)都可以通過 hibernate.properties 文件中的參數(或提供給 SessionFactory 的 properties 對象)進行配置。有關更多詳細信息,請參閱 Hibernate 文檔。
如果您的程序已經有一個現有的連接管理基礎結構,那么您可以給 openSession(Connection con) 方法提供一個連接,Hibernate 將使用您提供的連接。
操作數據庫對象
這一節描述如何寫到數據庫中,如何從數據庫裝入對象以及如何更新和查詢數據庫。
寫到數據庫
要寫到數據庫,我們將使用 SessionFactory 對象打開一個新的會話。然后,我們將創建想持久保存的對象并將它保存到會話中。接著,我們刷新(flush)會話,在連接上調用提交(commit),最后關閉(close)會話。
刷新會話會強制 Hibernate 把內存中的數據和數據庫同步起來。Hibernate 將定期自動刷新,但不能保證在什么時候進行。于是,我們將內存中的數據顯式刷新到數據庫,從而確保數據立即寫入數據庫。
在關閉會話之前,還必須確保提交了數據庫連接。
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();
從數據庫裝入對象
裝入對象就是使用對象的標識將對象調回到內存中的過程。這與我們在查詢數據庫中討論的查詢對象不同。
為了從數據庫裝入對象,我們同樣需要一個會話。我們還需要想裝入的對象的主鍵。就我們前面所編寫的示例來說,如果想將 Department 裝回到對象中,我們可以用表示 Department 的 Class 對象來調用 session.load 方法,我們的主鍵是“211”。
Session session = sessionFactory.openSession();
Department dept = (Department) session.load(Department.class, new Integer(211));
session.close();
更新數據庫
要更新一個對象,可以在創建該對象的那個會話中進行,也可以在一個完全不同的會話中進行。在同一個會話中更新對象很容易;只要修改對象的狀態就行了。要在不同的會話中更新對象,則必須裝入(或查詢)該對象,然后更新它。
同一個會話
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();
查詢數據庫
查詢數據庫有幾種方式。最簡單的方式是使用 session.find 方法。您必須使用 Hibernate 簡單但卻功能強大的面向對象的查詢語言來給 session.find 提供一個查詢。下面的示例演示了一個非常簡單的查詢。如果要進行更復雜的查詢,請參閱 Hibernate 文檔獲取更多詳細信息。
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'' ");
進行測試
既然我們有了會話對象并且知道了如何進行一些操作,那我們就可以在我們簡單的對象模型上編寫一些 CRUD 測試來看看 Hibernate 的實際運行情況。這是一種比 JUnit 測試好得多的測試方法。您可以隨意看看本文所附的源代碼中的 HibernateTest.java 測試用例。
結束語
在本文中,我們僅僅粗淺地討論了如何使用 Hibernate。我們在幾個 POJO(Plain Old Java Object,傳統的 Java 對象)的上下文中介紹 Hibernate API。不過,請注意廣泛的 Hibernate API 涵蓋了更高級的主題,如 one-to-many(一對多)映射、事務以及集合。既然您已“涉足”Hibernate,那應該能更自在地探索如何在編程工作中使用開放式源代碼產品了吧。
我們在一些項目中使用了 Hibernate,使用時遇到了一些障礙。我們發現,SourceForge 上的 Hibernate 論壇對于解答我們的問題是不可或缺的。在該論壇上,Hibernate 的主要開發人員非?;钴S,他們幾乎會解答所有貼子。
論壇的網址為:
http://sourceforge.net/forum/forum.php?forum_id=128638
Hibernate Web 頁面的網址:
http://hibernate.bluemars.net/
關于作者
Javid Jamae 是一位專攻企業應用程序和軟件方法學咨詢的獨立軟件顧問。您可以通過 javidjamae@yahoo.com 與 Javid 聯系。
Kulvir Singh Bhogal 的工作角色是 WebSphere 顧問,在全美實施 IBM 的電子商務戰略。您可以通過 kbhogal@us.ibm.com 與 Kulvir 聯系。