也許你聽說過Hibernate的大名,但可能一直不了解它,也許你一直渴望使用它進行開發,那麼本文正是你所需要的!在本文中,我向大家重點介紹Hibernate的核心API調用庫,並講解一下它的基本配置。
看完本文後,我相信你對什麼是ORM(對像/關系映射)以及它的優點會有一個深刻的認識,我們先通過一個簡單的例子開始來展現它的威力。
正如一些傳統的經典計算機文章大都會通過一個“hello,world”的例子開始講解一樣,我們也不例外,我們也將從一個相對簡單的例子來闡述Hibernate的開發方法,但如果要真正闡述Hibernate的一些重要思想,僅僅靠在屏幕上打印一些字符是遠遠不夠的,在我們的示例程序中,我們將創建一些對象,並將其保存在數據庫中,然後對它們進行更新和查詢。
閱讀導航
“Hello World”“Hello world”示例程序讓您對Hibernate有一個簡單的認識。
理解Hibernate的架構介紹Hibernate接口的主要功能。
核心接口Hibernate有5個核心接口,通過這幾個接口開發人員可以存儲和獲得持久對象,並且能夠進行事務控制
一個重要的術語:TypeType是Hibernate發明者發明的一個術語,它在整個構架中是一個非常基礎、有著強大功能的元素,一個Type對象能將一個Java類型映射到數據庫中一個表的字段中去。
策略接口Hibernate與某些其它開源軟件不同的還有一點――高度的可擴展性,這通過它的內置策略機制來實現。
基礎配置Hibernate可以配置成可在任何Java環境中運行,一般說來,它通常被用在2-3層的C/S模式的項目中,並被部署在服務端。
創建一個SessionFactory對象要創建一個SessionFactory對象,必須在Hibernate初始化時創建一個Configuration類的實例,並將已寫好的映射文件交由它處理。
“Hello World”
Hibernate應用程序定義了一些持久類,並且定義了這些類與數據庫表格的映射關系。在我們這個“Hello world”示例程序中包含了一個類和一個映射文件。讓我們看看這個簡單的持久類包含有一些什麼?映射文件是怎樣定義的?另外,我們該怎樣用Hibernate來操作這個持久類。
我們這個簡單示例程序的目的是將一些持久類存儲在數據庫中,然後從數據庫取出來,並將其信息正文顯示給用戶。其中Message正是一個簡單的持久類:,它包含我們要顯示的信息,其源代碼如下:
列表1 Message.Java 一個簡單的持久類
package hello;
public class Message {
private Long id;
private String text;
private Message nextMessage;
private Message() {}
public Message(String text) {
this.text = text;
}
public Long getId() {
return id;
}
private void setId(Long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Message getNextMessage() {
return nextMessage;
}
public void setNextMessage(Message nextMessage) {
this.nextMessage = nextMessage;
}
}
Message類有三個屬性:Message的id 、消息正文、以及一個指向下一條消息的指針。其中id屬性讓我們的應用程序能夠唯一的識別這條消息,通常它等同於數據庫中的主鍵,如果多個Message類的實例對象擁有相同的id,那它們代表數據庫某個表的同一個記錄。在這裡我們選擇了長整型作為我們的id值,但這不是必需的。Hibernate允許我們使用任意的類型來作為對象的id值,在後面我們會對此作詳細描述。
你可能注意到Message類的代碼類似於JavaBean的代碼風格,並且它有一個沒有參數的構造函數,在我們以後的代碼中我將繼續使用這種風格來編寫持久類的代碼。
Hibernate會自動管理Message類的實例,並通過內部機制使其持久化,但實際上Message對象並沒有實現任何關於Hibernate的類或接口,因此我們也可以將它作為一個普通的Java類來使用:
Message message = new Message("Hello World");
System.out.println( message.getText() );
以上這段代碼正是我們所期望的結果:它打印“hello world”到屏幕上。但這並不是我們的最終目標;實際上Hibernate與諸如EJB容器這樣的環境在持久層實現的方式上有很大的不同。我們的持久類(Message類)可以用在與容器無關的環境中,不像EJB必須要有EJB容器才能執行。為了能更清楚地表現這點,以下代碼將我們的一個新消息保存到數據庫中去:
Session session = getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
Message message = new Message("Hello World");
session.save(message);
tx.commit();
session.close();
以上這段代碼調用了Hibernate的Session和Transaction接口(關於getSessionFactory()方法我們將會馬上提到)。它相當於我們執行了以下SQL語句:
insert into MESSAGES (MESSAGE_ID, MESSAGE_TEXT, NEXT_MESSAGE_ID)
values (1, 'Hello World', null)
在以上的SQL語句中,MESSAGE_ID字段到底被初始化成了什麼值呢?由於我們並沒有在先前的代碼中為message對象的id屬性賦與初始值,那它是否為null呢?實際上Hibernate對id屬性作了特殊處理:由於它是一個對象的唯一標識,因此當我們進行save()調用時,Hibernate會為它自動賦予一個唯一的值(我們將在後面內容中講述它是如何生成這個值的)。
我們假設你已經在數據庫中創建了一個名為MESSAGE的表,那麼既然前面這段代碼讓我們將Message對象存入了數據庫中,那麼現在我們就要將它們一一取出來。下面這段代碼將按照字母順序,將數據庫中的所有Message對象取出來,並將它們的消息正文打印到屏幕上:
Session newSession = getSessionFactory().openSession();
Transaction newTransaction = newSession.beginTransaction();
List messages =newSession.find("from Message as m order by m.text asc");
System.out.println( messages.size() + " message(s) found:" );
for ( Iterator iter = messages.iterator(); iter.hasNext(); ) {
Message message = (Message) iter.next();
System.out.println( message.getText() );
}
newTransaction.commit();
newSession.close();
在以上這段代碼中,你可能被find()方法的這個參數困擾著:"from Message as m order by m.text asc",其實它是Hibernate自己定義的查詢語言,全稱叫Hibernate Query Language(HQL)。通俗地講HQL與SQL的關系差不多就是方言與普通話之間的關系,咋一看,你會覺得它有點類似於SQL語句。其實在find()調用時,Hibernate會將這段HQL語言翻譯成如下的SQL語句:
select m.MESSAGE_ID, m.MESSAGE_TEXT, m.NEXT_MESSAGE_ID
from MESSAGES m
order by m.MESSAGE_TEXT asc
以下就是運行結果:
1 message(s) found:
Hello World
如果你以前沒有ORM(對象-關系映射)的開發經驗,那你可能想在代碼的某個地方去尋找這段SQL語句,但在Hibernate中你可能會失望:它根本不存在!所有就SQL語句都是Hibernate動態生成的。
也許你會覺得還缺點什麼,對!僅憑以上代碼Hibernate是無法將我們的Message類持久化的。我們還需要一些更多的信息,這就是映射定義表!這個表在Hibernate中是以XML格式來體現的,它定義了Message類的屬性是怎樣與數據庫中的MESSAGES表的字段進行一一對應的,列表2是這個示例程序的映射配置文件清單:
列表2:示例程序的對象-關系映射表
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="hello.Message" table="MESSAGES">
<id name="id" column="MESSAGE_ID">
<generator class="increment"/>
</id>
<property name="text" column="MESSAGE_TEXT"/>
<many-to-one name="nextMessage" cascade="all" column="NEXT_MESSAGE_ID"/>
</class>
</hibernate-mapping>
以上這個文檔告訴Hibernate怎樣將Message類映射到MESSAGES表中,其中Message類的id屬性與表的MESSAGE_ID字段對應,text屬性與表的MESSAGE_TEXT字段對應,nextMessage屬性是一個多對一的關系,它與表中的NEXT_MESSAGE_ID相對應。
相對於有些開源項目來說,Hibernate的配置文件其實是很容易理解的。你可以輕松地修改與維護它。只要你定義好了持久類與數據庫中表字段的對應關系就行了,Hibernate會自動幫你生成SQL語句來對Message對象進行插入、更新、刪除、查找工作,你可以不寫一句SQL語句,甚至不需要懂得SQL語言!
現在讓我們做一個新的試驗,我們先取出第一個Message對象,然後修改它的消息正文,最後我們再生成一個新的Message對象,並將它作為第一個Message對象的下一條消息,其代碼如下:
列表3 更新一條消息
Session session = getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
// 1 is the generated id of the first message
Message message =(Message) session.load( Message.class, new Long(1) );
message.setText("Greetings Earthling");
Message nextMessage = new Message("Take me to your leader (please)");
message.setNextMessage( nextMessage );
tx.commit();
session.close();
以上這段代碼在調用時,Hibernate內部自動生成如下的SQL語句:
select m.MESSAGE_ID, m.MESSAGE_TEXT, m.NEXT_MESSAGE_ID
from MESSAGES m
where m.MESSAGE_ID = 1
insert into MESSAGES (MESSAGE_ID, MESSAGE_TEXT, NEXT_MESSAGE_ID)
values (2, 'Take me to your leader (please)', null)
update MESSAGES
set MESSAGE_TEXT = 'Greetings Earthling', NEXT_MESSAGE_ID = 2
where MESSAGE_ID = 1
當第一個Message對象的text屬性和nextMessage被程序修改時,請注意Hibernate是如何檢測到這種變化,並如何在數據庫中自動對它更新的。這實際上是Hibernate的一個很有價值的特色,我們把它稱為“自動髒數據檢測”,Hibernate的這個特色使得當我們修改一個持久對象的屬性後,不必顯式地通知Hibernate去將它在數據庫中進行更新。同樣的,當第一個Message對象調用setNextMessage()方法將第二個Message對象作為它的下一條消息的引用時,第二條消息會無需調用save()方法,便可以自動地保存在數據庫中。這種特色被稱為“級聯保存”,它也免去了我們顯式地對第二個Message對象調用save()方法之苦。
如果我們再運行先前的那段將數據庫中所有的Message對象都打印出來的代碼,那它的運行結果如下:
2 message(s) found:
Greetings Earthling
Take me to your leader (please)
“Hello world”示例程序現在介紹完畢。我們總算對Hibernate有了一個簡單的認識,下面我們將回過頭來,對Hibernate的主要API調用作一下簡要的介紹:
理解Hibernate的架構
當你想用Hibernate開發自己的基於持久層的應用時,第一件事情應當是熟悉它的編程接口。Hibernate的API接口設計得盡量簡潔明了,以方便開發人員。然而實際上由於ORM的復雜性,它的API一般都不可能設計得很簡單。但是別擔心,你沒有必要一下子了解所有的Hibernate的API接口。
我們將應用層放在了持久層的上部,實際上在傳統的項目中,應用層充當著持久層的一個客戶端角色。但對於一些簡單的項目來說,應用層和持久層並沒有區分得那麼清楚,這也沒什麼,在這種情況下你可以將應用層和持久層合並成了一層。
Hibernate的接口大致可以分為以下幾種類型:
· 一些被用戶的應用程序調用的,用來完成基本的創建、讀取、更新、刪除操作以及查詢操作的接口。這些接口是Hibernate實現用戶程序的商業邏輯的主要接口,它們包括Session、Transaction和Query。
· Hibernate用來讀取諸如映射表這類配置文件的接口,典型的代表有Configuration類。
· 回調(Callback)接口。它允許應用程序能對一些事件的發生作出相應的操作,例如Interceptor、Lifecycle和Validatable都是這一類接口。
· 一些可以用來擴展Hibernate的映射機制的接口,例如UserType、CompositeUserType和IdentifierGenerator。這些接口可由用戶程序來實現(如果有必要)。
Hibernate使用了J2EE架構中的如下技術:JDBC、JTA、JNDI。其中JDBC是一個支持關系數據庫操作的一個基礎層;它與JNDI和JTA一起結合,使得Hibernate可以方便地集成到J2EE應用服務器中去。
在這裡,我們不會詳細地去討論Hibernate API接口中的所有方法,我們只簡要講一下每個主要接口的功能,如果你想了解得更多的話,你可以在Hibernate的源碼包中的net.sf.hibernate子包中去查看這些接口的源代碼。下面我們依次講一下所有的主要接口:
核心接口
以下5個核心接口幾乎在任何實際開發中都會用到。通過這些接口,你不僅可以存儲和獲得持久對象,並且能夠進行事務控制。
Session接口
Session接口對於Hibernate 開發人員來說是一個最重要的接口。然而在Hibernate中,實例化的Session是一個輕量級的類,創建和銷毀它都不會占用很多資源。這在實際項目中確實很重要,因為在客戶程序中,可能會不斷地創建以及銷毀Session對象,如果Session的開銷太大,會給系統帶來不良影響。但值得注意的是Session對象是非線程安全的,因此在你的設計中,最好是一個線程只創建一個Session對象。
在Hibernate的設計者的頭腦中,他們將session看作介於數據連接與事務管理一種中間接口。我們可以將session想象成一個持久對象的緩沖區,Hibernate能檢測到這些持久對象的改變,並及時刷新數據庫。我們有時也稱Session是一個持久層管理器,因為它包含這一些持久層相關的操作,諸如存儲持久對象至數據庫,以及從數據庫從獲得它們。請注意,Hibernate 的session不同於JSP應用中的HttpSession。當我們使用session這個術語時,我們指的是Hibernate中的session,而我們以後會將HttpSesion對象稱為用戶session。
SessionFactory 接口
這裡用到了一個設計模式――工廠模式,用戶程序從工廠類SessionFactory中取得Session的實例。
令你感到奇怪的是SessionFactory並不是輕量級的!實際上它的設計者的意圖是讓它能在整個應用中共享。典型地來說,一個項目通常只需要一個SessionFactory就夠了,但是當你的項目要操作多個數據庫時,那你必須為每個數據庫指定一個SessionFactory。
SessionFactory在Hibernate中實際起到了一個緩沖區的作用,它緩沖了Hibernate自動生成的SQL語句和一些其它的映射數據,還緩沖了一些將來有可能重復利用的數據。
Configuration 接口
Configuration接口的作用是對Hibernate進行配置,以及對它進行啟動。在Hibernate的啟動過程中,Configuration類的實例首先定位映射文檔的位置,讀取這些配置,然後創建一個SessionFactory對象。
雖然Configuration接口在整個Hibernate項目中只扮演著一個很小的角色,但它是啟動hibernate時你所遇到的每一個對象。
Transaction 接口
Transaction接口是一個可選的API,你可以選擇不使用這個接口,取而代之的是Hibernate的設計者自己寫的底層事務處理代碼。 Transaction接口是對實際事務實現的一個抽象,這些實現包括JDBC的事務、JTA中的UserTransaction、甚至可以是CORBA事務。之所以這樣設計是能讓開發者能夠使用一個統一事務的操作界面,使得自己的項目可以在不同的環境和容器之間方便地移值。
Query和Criteria接口
Query接口讓你方便地對數據庫及持久對象進行查詢,它可以有兩種表達方式:HQL語言或本地數據庫的SQL語句。Query經常被用來綁定查詢參數、限制查詢記錄數量,並最終執行查詢操作。
Criteria接口與Query接口非常類似,它允許你創建並執行面向對象的標准化查詢。
值得注意的是Query接口也是輕量級的,它不能在Session之外使用。
Callback 接口
當一些有用的事件發生時――例如持久對象的載入、存儲、刪除時,Callback接口會通知Hibernate去接收一個通知消息。一般而言,Callback接口在用戶程序中並不是必須的,但你要在你的項目中創建審計日志時,你可能會用到它。
一個重要的術語:Type
Hibernate的設計者們發明了一個術語:Type,它在整個構架中是一個非常基礎、有著強大功能的元素。一個Type對象能將一個Java類型映射到數據庫中一個表的字段中去(實際上,它可以映射到表的多個字段中去)。持久類的所有屬性都對應一個type。這種設計思想使用Hibernate有著高度的靈活性和擴展性。
Hibernate內置很多type類型,幾乎包括所有的Java基本類型,例如Java.util.Currency、Java.util.calendar、byte[]和Java.io.Serializable。
不僅如此,Hibernate還支持用戶自定義的type,通過實現接口UserType和接口CompositeUserType,你可以加入自己的type。你可以利用這種特色讓你的項目中使用自定義的諸如Address、Name這樣的type,這樣你就可以獲得更大的便利,讓你的代碼更優雅。自定義type在Hibernate中是一項核心特色,它的設計者鼓勵你多多使用它來創建一個靈活、優雅的項目!
策略接口
Hibernate與某些其它開源軟件不同的還有一點――高度的可擴展性,這通過它的內置策略機制來實現。當你感覺到Hibernate的某些功能不足,或者有某些缺陷時,你可以開發一個自己的策略來替換它,而你所要做的僅僅只是繼承它的某個策略接口,然後實現你的新策略就可以了,以下是它的策略接口:
· 主鍵的生成 (IdentifierGenerator 接口)
· 本地SQL語言支持 (Dialect 抽象類)
· 緩沖機制 (Cache 和CacheProvider 接口)
· JDBC 連接管理 (ConnectionProvider接口)
· 事務管理 (TransactionFactory, Transaction, 和 TransactionManagerLookup 接口)
· ORM 策略 (ClassPersister 接口)
· 屬性訪問策略 (PropertyAccessor 接口)
· 代理對象的創建 (ProxyFactory接口)
Hibernate為以上所列的機制分別創建了一個缺省的實現,因此如果你只是要增強它的某個策略的功能的話,只需簡單地繼承這個類就可以了,沒有必要從頭開始寫代碼。
以上就是Hibernate的一些核心接口,但當我們真正開始用它進行開發時,你的腦海裡可能總會有一個疑問:我是通過什麼方式,並從哪裡取得Session的呢?以下我們就解答這個問題。
基礎配置
現在回顧一下我們先前的內容:我們寫出了一個示例程序,並簡要地講解了Hibernate的一些核心類。但要真正使你的項目運行起來,還有一件事必須要做:配置。Hibernate可以配置成可在任何Java環境中運行,一般說來,它通常被用在2-3層的C/S模式的項目中,並被部署在服務端。在這種項目中,Web浏覽器、或Java GUI程序充當者客戶端。盡管我們的焦點主要是集中在多層web應用,但實際上在一些基於命令行的應用中也可以使用Hibernate。並且,對Hibernate的配置在不同的環境下都會不同,Hibernate運行在兩種環境下:可管理環境和不可管理環境
· 可管理環境――這種環境可管理如下資源:池資源管理,諸如數據庫連接池和,還有事務管理、安全定義。一些典型的J2EE服務器(JBoss、Weblogic、WebSphere)已經實現了這些。
· 不可管理環境――只是提供了一些基本的功能,諸如像Jetty或Tomcat這樣的servlet容器環境。一個普通的Java桌面應用或命令行程序也可以認為是處於這種環境下。這種環境不能提供自動事務處理、資源管理或安全管理,這些都必須由應用程序自己來定義。
Hibernate的設計者們將這兩種環境設計了一個統一的抽象界面,因此對於開發者來說只有一種環境:可管理環境。如果實際項目是建立在諸如Tomcat這類不可管理的環境中時,那Hibernate將會使用它自己的事務處理代碼和JDBC連接池,使其變為一個可管理環境。
對於可管理的環境而言,Hibernate會將自己集成在這種環境中。對於開發者而言,你所要做的工作非常簡單:只需從一個Configuration類中創建一個SessionFactory類就可以了。
創建一個SessionFactory對象
為了能創建一個SessionFactory對象,你必須在Hibernate初始化時創建一個Configuration類的實例,並將已寫好的映射文件交由它處理。這樣,Configuration對象就可以創建一個SessionFactory對象,當SessionFactory對象創建成功後,Configuration對象就沒有用了,你可以簡單地拋棄它。如下是示例代碼:
Configuration cfg = new Configuration();
cfg.addResource("hello/Message.hbm.xml");
cfg.setProperties( System.getProperties() );
SessionFactory sessions = cfg.buildSessionFactory();
在以上代碼中,Message.hb.xml這個映射文件的位置比較特殊,它與當前的classpath相關。例如classpath包含當前目錄,那在上述代碼中的Message.hbm.xml映射文件就可以保存在當前目錄下的hello目錄中。
作為一種約定,Hibernate的映射文件默認以.htm.xml作為其擴展名。另一個約定是堅持為每一個持久類寫一個配置文件,想一想如果你將所有持久類的映射寫入一個單獨的配置文件中的話,那這個配置文件肯定非常龐大,不易維護。但這裡又出現了一個新問題:如果為每個類寫一個配置文件的話,這麼多的配置文件應該存放在哪裡呢?
Hibernate推薦你將每個映射文件保存在與持久類相同的目錄下,並且與持久類同名。例如我們第一個示例程序中的Message持久類放在hello目錄下,那你必須在這個目錄下存放名為Message.hbm.xml的映射文件。這樣一個持久類都有自己的一個映射文件,避免了出現像struts項目中的“struts-config.xml地獄”的情況。如果你不遵循這種規定,那你必須手動地用addResource()方法將一個個的映射文件載入;但你如果遵循這種規定,那你可以方便地用addClass()方法同時將持久類和它的映射文件載入,以下是體現這種便利性的示例代碼:
SessionFactory sessions = new Configuration()
.addClass(org.hibernate.auction.model.Item.class)
.addClass(org.hibernate.auction.model.Category.class)
.addClass(org.hibernate.auction.model.Bid.class)
.setProperties( System.getProperties() )
.buildSessionFactory();
當然,Hibernate的映射文件還有很多其它的配置選項,比如數據庫連接的設定,或是能夠改變Hibernate運行時行為的一些設定。所有的設置可能是非常龐雜的,足以讓你喘不過氣來,但是不必擔心,因為Hibernate為絕大多數值都設定了一個合理缺省值,你只需要修改這些配置文件中的極小一部分值。
你可以通過以下幾種方式來修改Hibernate的系統配置參數:
· 將一個Java.util.Properties實例作為參數傳給Configuration類的setProperties()方法。
· 在Hibernate啟動時用Java –Dproperty=value的方式設置值。
· 在classpath可以找到的路徑下創建一個名為hibernate.properties的配置文件。
· 在classpath可以找到的路徑下創建一個名為hibernate.cfg.xml的文件,並在其<property>標簽中定義屬性值。
以上就是對Hibernate的一個大致介紹,如果你想知道得更多,那本文還是遠遠不夠的,我將陸續推出更多關於Hibernate的資料。但有一點是毫無疑問的:它的確是一個非常優秀的持久層解決方案!