posts - 20,  comments - 7,  trackbacks - 0
          ?
          Original Author: 夏昕<xiaxin@gmail.com>


          本文是由筆者2003 年底一個咨詢項目中,為客戶做的持久層設(shè)計培訓(xùn)文案整理而來。其中的內(nèi)容涉及Hibernate 的使用,以及一部分筆者實際咨詢項目中的經(jīng)驗積累,另一方面,大部分是筆者在Hibernate 的官方論壇中與眾多技術(shù)專家交流所得。既來于斯,則歸于斯。希望能聊有所用。

          本文并非試圖替代Hibernate Reference,相對而言,Hibernate Reference的編寫目的是為開發(fā)者提供更簡便的條目索引,而本文目標(biāo)則在于為開發(fā)人員提供一個入門和掌握Hibernate的途徑。本文需結(jié)合Hibernate Reference使用。筆者好友曹曉鋼義務(wù)組織了Hibernate文檔的漢化工作,在此對其辛勤勞作致敬。中文版Hibernate Reference將被包含在Hibernate下個官方Release中,目前可通過http://www.redsaga.com獲取中文版Hibernate Reference的最新版本。本文中如果發(fā)現(xiàn)問題和錯誤,請隨時聯(lián)系筆者,以免誤導(dǎo)他人。本文轉(zhuǎn)載不限,不過請保持本文完整。萬分感謝!


          Hibernate 開發(fā)指南.......................................................................................................1
          準備工作..........................................................................................................3
          構(gòu)建Hibernate基礎(chǔ)代碼...............................................................................3
          由數(shù)據(jù)庫產(chǎn)生基礎(chǔ)代碼...........................................................................4
          Hibernate配置..............................................................................................15
          第一段代碼....................................................................................................17
          Hibernate基礎(chǔ)語義......................................................................................19
          Configuration ........................................................................................19
          SessionFactory.......................................................................................20
          Session....................................................................................................20
          Hibernate高級特性......................................................................................................22
          XDoclet與Hibernate映射...........................................................................22
          數(shù)據(jù)檢索........................................................................................................31
          Criteria Query...............................................................................31
          Criteria查詢表達式................................................................31
          Criteria高級特性....................................................................33
          限定返回的記錄范圍.............................................................33
          對查詢結(jié)果進行排序.............................................................33
          Hibernate Query Language (HQL).........................................34
          數(shù)據(jù)關(guān)聯(lián)........................................................................................................35
          一對一關(guān)聯(lián).............................................................................35
          一對多關(guān)聯(lián).............................................................................37
          ? 單向一對多關(guān)系......................................................37
          ? 雙向一對多關(guān)系......................................................42
          多對多關(guān)聯(lián).............................................................................47
          數(shù)據(jù)訪問........................................................................................................54
          PO和VO...............................................................................................54
          關(guān)于unsaved-value ...............................................................................57
          Inverse和Cascade.........................................................................59
          延遲加載(Lazy Loading)............................................................59
          事務(wù)管理........................................................................................................63
          基于JDBC的事務(wù)管理:.....................................................................64
          基于JTA的事務(wù)管理:.......................................................................65
          鎖(locking).........................................................................................68
          悲觀鎖(Pessimistic Locking).......................................68
          樂觀鎖(Optimistic Locking)..........................................69
          Hibernate分頁..........................................................................................73
          Cache管理....................................................................................................75
          Session管理...............................................................................................79
          編后贅言................................................................................................................84


          Hibernate Quick Start

          準備工作
          1. 下載Ant軟件包,解壓縮(如C:\ant\)。并將其bin目錄(如c:\ant\bin)添加到系統(tǒng)
          PATH 中。
          2. 下載Hibernate、Hibernate-Extension和Middlegen-Hibernate軟件包的最新版本。
          http://prdownloads.sourceforge.net/hibernate/


          構(gòu)建Hibernate 基礎(chǔ)代碼
          Hibernate基礎(chǔ)代碼包括:
          1. POJO
          POJO 在Hibernate 語義中理解為數(shù)據(jù)庫表所對應(yīng)的Domain Object。這里的POJO
          就是所謂的“Plain Ordinary Java Object”,字面上來講就是無格式普通Java 對象,簡
          單的可以理解為一個不包含邏輯代碼的值對象(Value Object 簡稱VO)。
          一個典型的POJO:
          public class TUser implements Serializable {
          private String name;
          public User(String name) {
          this.name = name;
          }
          /** default constructor */
          public User() {
          }
          public String getName() {
          return this.name;
          }
          public void setName(String name) {
          this.name = name;
          }
          }
          2. Hibernate 映射文件
          Hibernate 從本質(zhì)上來講是一種“對象-關(guān)系型數(shù)據(jù)映射”(Object Relational
          Mapping 簡稱ORM)。前面的POJO在這里體現(xiàn)的就是ORM中Object層的語義,
          而映射(Mapping)文件則是將對象(Object)與關(guān)系型數(shù)據(jù)(Relational)相關(guān)聯(lián)
          的紐帶,在Hibernate中,映射文件通常以“.hbm.xml”作為后綴。
          構(gòu)建Hibernate基礎(chǔ)代碼通常有以下途徑:
          1. 手工編寫
          2. 直接從數(shù)據(jù)庫中導(dǎo)出表結(jié)構(gòu),并生成對應(yīng)的ORM文件和Java 代碼。
          這是實際開發(fā)中最常用的方式,也是這里所推薦的方式。
          通過直接從目標(biāo)數(shù)據(jù)庫中導(dǎo)出數(shù)據(jù)結(jié)構(gòu),最小化了手工編碼和調(diào)整的可能性,從而
          最大程度上保證了ORM文件和Java 代碼與實際數(shù)據(jù)庫結(jié)構(gòu)相一致。
          3. 根據(jù)現(xiàn)有的Java 代碼生成對應(yīng)的映射文件,將Java 代碼與數(shù)據(jù)庫表相綁定。
          通過預(yù)先編寫好的POJO 生成映射文件,這種方式在實際開發(fā)中也經(jīng)常使用,特別
          是結(jié)合了xdoclet 之后顯得尤為靈活,其潛在問題就是與實際數(shù)據(jù)庫結(jié)構(gòu)之間可能
          出現(xiàn)的同步上的障礙,由于需要手工調(diào)整代碼,往往調(diào)整的過程中由于手工操作的
          疏漏,導(dǎo)致最后生成的配置文件錯誤,這點需要在開發(fā)中特別注意。
          結(jié)合xdoclet,由POJO 生成映射文件的技術(shù)我們將在“高級特性”章節(jié)中進行探討。
          由數(shù)據(jù)庫產(chǎn)生基礎(chǔ)代碼
          通過Hibernate官方提供的MiddleGen for Hibernate 和Hibernate_Extension工具包,我
          們可以很方便的根據(jù)現(xiàn)有數(shù)據(jù)庫,導(dǎo)出數(shù)據(jù)庫表結(jié)構(gòu),生成ORM和POJO。
          1) 首先,將Middlegen-Hibernate軟件包解壓縮( 如解壓縮到C:\Middlegen\ )。
          2) 配置目標(biāo)數(shù)據(jù)庫參數(shù)
          進入MiddleGen 目錄下的\config\database 子目錄,根據(jù)我們實際采用的數(shù)據(jù)庫打開
          對應(yīng)的配置文件。如這里我們用的是mysql數(shù)據(jù)庫,對應(yīng)的就是mysql.xml 文件。
          <property name="database.script.file"
          value="${src.dir}/sql/${name}-mysql.sql"/>
          <property name="database.driver.file"
          value="${lib.dir}/mysql.jar"/>
          <property name="database.driver.classpath"
          value="${database.driver.file}"/>
          <property name="database.driver"
          value="org.gjt.mm.mysql.Driver"/>
          <property name="database.url"
          value="jdbc:mysql://localhost/sample"/>
          <property name="database.userid"
          value="user"/>
          <property name="database.password"
          value="mypass"/>
          <property name="database.schema"
          value=""/>
          <property name="database.catalog"
          value=""/>
          <property name="jboss.datasource.mapping"
          value="mySQL"/>
          其中下劃線標(biāo)準的部分是我們進行配置的內(nèi)容,分別是數(shù)據(jù)url以及數(shù)據(jù)庫用
          戶名和密碼。
          3) 修改Build.xml
          修改MiddleGen 根目錄下的build.xml 文件,此文件是Middlegen-Hibernate 的Ant
          構(gòu)建配置。Middlegen-Hibernate將根據(jù)build.xml 文件中的具體參數(shù)生成數(shù)據(jù)庫表映射
          文件??膳渲玫捻椖堪ǎ?br />a) 目標(biāo)數(shù)據(jù)庫配置文件地址
          查找關(guān)鍵字 ”!ENTITY”,得到:
          <!DOCTYPE project [
          <!ENTITY database SYSTEM
          "file:./config/database/hsqldb.xml">
          ]>
          默認情況下,MiddleGen 采用的是hsqldb.xml,將其修改為我們所用的數(shù)據(jù)
          庫配置文件(mysql.xml):
          <!DOCTYPE project [
          <!ENTITY database SYSTEM
          "file:./config/database/mysql.xml">
          ]>
          b) Application name
          查找:
          <property name="name" value="airline"/>
          “aireline”是MiddleGen原始配置中默認的 Application Name,將其修改為我們
          所希望的名稱,如“HibernateSample”:
          <property name="name" value="HibernateSample"/>
          c) 輸出目錄
          查找關(guān)鍵字“name="build.gen-src.dir"”,得到:
          <property name="build.gen-src.dir"
          value="${build.dir}/gen-src"/>
          修改value="${build.dir}/gen-src"使其指向我們所期望的輸出目錄,
          這里我們修改為:
          <property name="build.gen-src.dir"
          value="C:\sample"/>
          d) 對應(yīng)代碼的Package name
          查找關(guān)鍵字“destination”,得到:
          <hibernate
          destination="${build.gen-src.dir}"
          package="${name}.hibernate"
          genXDocletTags="false"
          genIntergratedCompositeKeys="false"
          javaTypeMapper=
          "middlegen.plugins.hibernate.HibernateJavaTypeMapper"
          />
          可以看到,hibernate 節(jié)點package 屬性的默認設(shè)置實際上是由前面的
          Application Name (${name})和“.hibernate”組合而成,根據(jù)我們的需要,
          將其改為:
          <hibernate
          destination="${build.gen-src.dir}"
          package="org.hibernate.sample"
          genXDocletTags="true"
          genIntergratedCompositeKeys="false"
          javaTypeMapper=
          "middlegen.plugins.hibernate.HibernateJavaTypeMapper"
          />
          這里還有一個屬性genXDocletTags,如果設(shè)置為true,則生成的代碼將包含
          xdoclet tag,這為以后在開發(fā)過程中借助xdoclet進行映射調(diào)整提供了幫助。關(guān)
          于Hibernate的xdoclet使用,請參見“高級特性”中的相關(guān)內(nèi)容。
          注意,如果使用的數(shù)據(jù)庫為SQLServer,需要將build.xml 中如下部分(下劃
          線部分)刪除,否則Middlegen會報出找不到表的錯誤。
          <middlegen
          appname="${name}"
          prefsdir="${src.dir}"
          gui="${gui}"
          databaseurl="${database.url}"
          initialContextFactory="${java.naming.factory.initial}"
          providerURL="${java.naming.provider.url}"
          datasourceJNDIName="${datasource.jndi.name}"
          driver="${database.driver}"
          username="${database.userid}"
          password="${database.password}"
          schema="${database.schema}"
          catalog="${database.catalog}"
          >
          至此為止,MiddleGen 已經(jīng)配置完畢,在MiddleGen 根目錄下運行ant,就將出現(xiàn)
          MiddleGen的界面:
          可以看到,數(shù)據(jù)庫中的表結(jié)構(gòu)已經(jīng)導(dǎo)入到MiddleGen 的操作界面中,選定數(shù)據(jù)庫
          表視圖中的表元素,我們即可調(diào)整各個數(shù)據(jù)庫表的屬性。
          1 Domain Class Name
          對應(yīng)POJO 的類名
          2 Key Generator
          主鍵產(chǎn)生器
          可選項說明:
          1) Assigned

          ② ③


          ⑥ ⑦



          主鍵由外部程序負責(zé)生成,無需Hibernate參與。
          2) hilo
          通過hi/lo 算法實現(xiàn)的主鍵生成機制,需要額外的數(shù)據(jù)庫表保存主
          鍵生成歷史狀態(tài)。
          3) seqhilo
          與hilo 類似,通過hi/lo 算法實現(xiàn)的主鍵生成機制,只是主鍵歷史
          狀態(tài)保存在Sequence中,適用于支持Sequence的數(shù)據(jù)庫,如Oracle。
          4) increment
          主鍵按數(shù)值順序遞增。此方式的實現(xiàn)機制為在當(dāng)前應(yīng)用實例中維持
          一個變量,以保存著當(dāng)前的最大值,之后每次需要生成主鍵的時候
          將此值加1作為主鍵。
          這種方式可能產(chǎn)生的問題是:如果當(dāng)前有多個實例訪問同一個數(shù)據(jù)
          庫,那么由于各個實例各自維護主鍵狀態(tài),不同實例可能生成同樣
          的主鍵,從而造成主鍵重復(fù)異常。因此,如果同一數(shù)據(jù)庫有多個實
          例訪問,此方式必須避免使用。
          5) identity
          采用數(shù)據(jù)庫提供的主鍵生成機制。如DB2、SQL Server、MySQL
          中的主鍵生成機制。
          6) sequence
          采用數(shù)據(jù)庫提供的sequence 機制生成主鍵。如Oralce 中的
          Sequence。
          7) native
          由Hibernate根據(jù)底層數(shù)據(jù)庫自行判斷采用identity、hilo、sequence
          其中一種作為主鍵生成方式。
          8) uuid.hex
          由Hibernate基于128 位唯一值產(chǎn)生算法生成16 進制數(shù)值(編碼后
          以長度32 的字符串表示)作為主鍵。
          9) uuid.string
          與uuid.hex類似,只是生成的主鍵未進行編碼(長度16)。在某些
          數(shù)據(jù)庫中可能出現(xiàn)問題(如PostgreSQL)。
          10) foreign
          使用外部表的字段作為主鍵。
          一般而言,利用uuid.hex 方式生成主鍵將提供最好的性能和數(shù)據(jù)庫平臺適
          應(yīng)性。
          另外由于常用的數(shù)據(jù)庫,如Oracle、DB2、SQLServer、MySql 等,都提
          供了易用的主鍵生成機制(Auto-Increase 字段或者Sequence)。我們可以在數(shù)
          據(jù)庫提供的主鍵生成機制上,采用generator-class=native的主鍵生成方式。
          不過值得注意的是,一些數(shù)據(jù)庫提供的主鍵生成機制在效率上未必最佳,
          大量并發(fā)insert數(shù)據(jù)時可能會引起表之間的互鎖。
          數(shù)據(jù)庫提供的主鍵生成機制,往往是通過在一個內(nèi)部表中保存當(dāng)前主鍵狀
          態(tài)(如對于自增型主鍵而言,此內(nèi)部表中就維護著當(dāng)前的最大值和遞增量),
          之后每次插入數(shù)據(jù)會讀取這個最大值,然后加上遞增量作為新記錄的主鍵,之
          后再把這個新的最大值更新回內(nèi)部表中,這樣,一次Insert操作可能導(dǎo)致數(shù)據(jù)
          庫內(nèi)部多次表讀寫操作,同時伴隨的還有數(shù)據(jù)的加鎖解鎖操作,這對性能產(chǎn)生
          了較大影響。
          因此,對于并發(fā)Insert要求較高的系統(tǒng),推薦采用uuid.hex 作為主鍵生成
          機制。
          3 如果需要采用定制的主鍵產(chǎn)生算法,則在此處配置主鍵生成器,主鍵生成器必
          須實現(xiàn)net.sf.hibernate.id.IdentifierGenerator 接口。
          4 Schema Name
          數(shù)據(jù)庫Schema Name。
          5 Persister
          自定義持久類實現(xiàn)類類名。如果系統(tǒng)中還需要Hibernate 之外的持久層實
          現(xiàn)機制,如通過存儲過程得到目標(biāo)數(shù)據(jù)集,甚至從LDAP中獲取數(shù)據(jù)來填
          充我們的POJO。
          6 Enable proxies
          是否使用代理(用于延遲加載[Lazy Loading])。
          7 Dynamic Update
          如果選定,則生成Update SQL 時不包含未發(fā)生變動的字段屬性,這樣可
          以在一定程度上提升SQL執(zhí)行效能。
          8 Mutable
          類是否可變,默認為選定狀態(tài)(可變)。如果不希望應(yīng)用程序?qū)Υ祟悓?yīng)
          的數(shù)據(jù)記錄進行修改(如對于數(shù)據(jù)庫視圖),則可將取消其選定狀態(tài),之
          后對此類的Delete和Update操作都將失效。
          9 Implement the Lifecyle interface
          是否實現(xiàn)Lifecyle接口。Lifecyle接口提供了數(shù)據(jù)固化過程中的控制機制,
          通過實現(xiàn)Lifecyle接口,我們可以在數(shù)據(jù)庫操作中加入回調(diào)(Call Back)
          機制,如在數(shù)據(jù)庫操作之前,之后觸發(fā)指定操作。
          10 Implement the Validatable interface
          是否實現(xiàn)Validatable接口。通過實現(xiàn)Validatable接口,我們可以在數(shù)據(jù)被
          固化到數(shù)據(jù)庫表之前對其合法性進行驗證。
          值得注意的是,通過實現(xiàn)Lifecyle接口,我們同樣可以在數(shù)據(jù)操作之前驗
          證數(shù)據(jù)合法性,不同的是,Validatable 接口中定義的validate 方法可能會
          被調(diào)用多次,因此設(shè)計中應(yīng)避免在Validatable 接口的validate 方法實現(xiàn)中
          加入業(yè)務(wù)邏輯的驗證。
          以上是針對Class的設(shè)置,同樣,在MiddleGen中,我們也可以設(shè)定字段屬性。在
          MiddleGen中選定某個字段,界面下方即出現(xiàn)字段設(shè)置欄:
          在這里我們可以設(shè)置字段的屬性,其中:
          1 Hibernate mapping specialty
          映射類型:
          Key :主鍵
          Property :屬性
          Version :用于實現(xiàn)optimistic locking,參見“高級特性”章節(jié)中關(guān)
          于optimistic locking的描述
          2 Java property name



          ④ ⑤
          字段對應(yīng)的Java 屬性名
          3 Java Type
          字段對應(yīng)的Java 數(shù)據(jù)類型
          4 Column updateable
          生成Update SQL時是否包含本字段。
          5 Column insertable
          生成Insert SQL時是否包含本字段。
          單擊窗口頂部的Generate 按鈕,MiddleGen 即為我們生成這些數(shù)據(jù)庫表所對應(yīng)的
          Hibernate映射文件。在MiddleGen根目錄下的\build\gen-src\net\hibernate\sample目錄中,
          我們可以看到對應(yīng)的以.hbm.xml 作為后綴的多個映射文件,每個映射文件都對應(yīng)了數(shù)
          據(jù)庫的一個表。
          僅有映射文件還不夠,我們還需要根據(jù)這些文件生成對應(yīng)的POJO。
          POJO 的生成工作可以通過Hibernate Extension 來完成,Hibernate Extension 的
          tools\bin目錄下包含三個工具:
          1. hbm2java.bat
          根據(jù)映射文件生成對應(yīng)的POJO。通過MiddleGen 我們已經(jīng)得到了映射文件,
          下一步就是通過hbm2java.bat工具生成對應(yīng)的POJO。
          2. class2hbm.bat
          根據(jù)POJO class 生成映射文件,這個工具很少用到,這里也就不再詳細介紹。
          3. ddl2hbm.bat
          由數(shù)據(jù)庫導(dǎo)出庫表結(jié)構(gòu),并生成映射文件以及POJO。這個功能與MiddleGen
          的功能重疊,但由于目前還不夠成熟(實際上已經(jīng)被廢棄,不再維護),提供
          的功能也有限,所以我們還是采用MiddleGen生成映射文件,之后由hbm2java
          根據(jù)映射文件生成POJO 的方式。
          為了使用以上工具,首先我們需要配置一些參數(shù),打開tools\bin\setenv.bat 文件,修改
          其中的JDBC_DRIVER和HIBERNATE_HOME環(huán)境變量,使其指向我們的實際JDBC Driver
          文件和Hibernate所在目錄,如
          set JDBC_DRIVER=c:\mysql\mysql.jar
          set HIBERNATE_HOME=c:\hibernate
          同時檢查一下環(huán)境變量CP中的各個項目中是否實際存在,特別是%CORELIB%下的jar
          文件,某些版本的發(fā)行包中,默認配置中的文件名與實際的文件名有所出入(如
          %CORELIB%\commons-logging.jar, 在Hibernate 發(fā)行包中,可能實際的文件名是
          commons-logging-1.0.3.jar,諸如此類)。
          使用hbm2java,根據(jù)MiddleGen生成的映射文件生成Java 代碼:
          打開Command Window,在tools\bin目錄下執(zhí)行:
          hbm2java c:\sample\org\hibernate\sample\*.xml --output=c:\sample\
          即可生成對應(yīng)的POJO。生成的POJO 保存在我們指定的輸出目錄下(c:\sample)。
          目前為止,我們已經(jīng)完成了通過MiddleGen 產(chǎn)生Hibernate 基礎(chǔ)代碼的工作。配置
          MiddleGen 也許并不是一件輕松的事情,對于Eclipse 的用戶而言,目前已經(jīng)出現(xiàn)了好幾個
          Hibernate 的Plugin,通過這些Plugin 我們可以更加輕松的完成上述工作,具體的使用方式
          請參見附錄。
          Hibernate 配置
          前面已經(jīng)得到了映射文件和POJO,為了使Hibernate 能真正運作起來,我們還需要一
          個配置文件。
          Hibernate同時支持xml格式的配置文件,以及傳統(tǒng)的properties 文件配置方式,不過這
          里建議采用xml 型配置文件。xml配置文件提供了更易讀的結(jié)構(gòu)和更強的配置能力,可以直
          接對映射文件加以配置,而在properties 文件中則無法配置,必須通過代碼中的Hard Coding
          加載相應(yīng)的映射文件。下面如果不作特別說明,都指的是基于xml格式文件的配置方式。
          配置文件名默認為“hibernate.cfg.xml”(或者hibernate.properties),Hibernate 初始化期
          間會自動在CLASSPATH 中尋找這個文件,并讀取其中的配置信息,為后期數(shù)據(jù)庫操作做好
          準備。
          配置文件應(yīng)部署在CLASSPATH 中,對于Web 應(yīng)用而言,配置文件應(yīng)放置在在
          \WEB-INF\classes 目錄下。
          一個典型的hibernate.cfg.xml配置文件如下:
          <?xml version="1.0" encoding="utf-8"?>
          <!DOCTYPE hibernate-configuration
          PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
          "http://hibernate.sourceforge.net/hibernate-configuration-2.0.
          dtd">
          <hibernate-configuration>
          <!—- SessionFactory 配置 -->
          <session-factory>
          <!—- 數(shù)據(jù)庫URL -->
          <property name="hibernate.connection.url">
          jdbc:mysql://localhost/sample
          </property>
          <!—- 數(shù)據(jù)庫JDBC驅(qū)動 -->
          <property name="hibernate.connection.driver_class">
          org.gjt.mm.mysql.Driver
          </property>
          <!—- 數(shù)據(jù)庫用戶名 -->
          <property name="hibernate.connection.username">
          User
          </property>
          <!—- 數(shù)據(jù)庫用戶密碼 -->
          <property name="hibernate.connection.password">
          Mypass
          </property>
          <!--dialect ,每個數(shù)據(jù)庫都有其對應(yīng)的Dialet以匹配其平臺特性 -->
          <property name="dialect">
          net.sf.hibernate.dialect.MySQLDialect
          </property>
          <!—- 是否將運行期生成的SQL輸出到日志以供調(diào)試 -->
          <property name="hibernate.show_sql">
          True
          </property>
          <!—- 是否使用數(shù)據(jù)庫外連接 -->
          <property name="hibernate.use_outer_join">
          True
          </property>
          <!—- 事務(wù)管理類型,這里我們使用JDBC Transaction -->
          <property name="hibernate.transaction.factory_class">
          net.sf.hibernate.transaction.JDBCTransactionFactory
          </property>
          <!—映射文件配置,注意配置文件名必須包含其相對于根的全路徑 -->
          <mapping resource="net/xiaxin/xdoclet/TUser.hbm.xml"/>
          <mapping resource="net/xiaxin/xdoclet/TGroup.hbm.xml"/>
          </session-factory>
          </hibernate-configuration>
          一個典型的hibernate.properties配置文件如下:
          hibernate.dialect net.sf.hibernate.dialect.MySQLDialect
          hibernate.connection.driver_class org.gjt.mm.mysql.Driver
          hibernate.connection.driver_class com.mysql.jdbc.Driver
          hibernate.connection.url jdbc:mysql:///sample
          hibernate.connection.username user
          hibernate.connection.password mypass
          第一段代碼
          上面我們已經(jīng)完成了Hiberante 的基礎(chǔ)代碼,現(xiàn)在先從一段最簡單的代碼入手,感受一
          下Hibernate所提供的強大功能。
          下面這段代碼是一個JUnit TestCase,演示了TUser 對象的保存和讀取。考慮到讀者可
          能沒有JUnit的使用經(jīng)驗,代碼中加入了一些JUnit相關(guān)注釋。
          public class HibernateTest extends TestCase {
          Session session = null;
          /**
          * JUnit中setUp方法在TestCase初始化的時候會自動調(diào)用
          * 一般用于初始化公用資源
          * 此例中,用于初始化Hibernate Session
          */
          protected void setUp(){
          try {
          /**
          * 采用hibernate.properties配置文件的初始化代碼:
          * Configuration config = new Configuration();
          * config.addClass(TUser.class);
          */
          //采用hibernate.cfg.xml配置文件
          //請注意初始化Configuration時的差異:
          // 1.Configuration的初始化方式
          // 2.xml文件中已經(jīng)定義了Mapping文件,因此無需再Hard Coding導(dǎo)入
          // POJO文件的定義
          Configuration config = new Configuration().configure();
          SessionFactory sessionFactory =
          config.buildSessionFactory();
          session = sessionFactory.openSession();
          } catch (HibernateException e) {
          e.printStackTrace();
          }
          }
          /**
          * 與setUp方法相對應(yīng),JUnit TestCase執(zhí)行完畢時,會自動調(diào)用tearDown方法
          * 一般用于資源釋放
          * 此例中,用于關(guān)閉在setUp方法中打開的Hibernate Session
          */
          protected void tearDown(){
          try {
          session.close();
          } catch (HibernateException e) {
          e.printStackTrace();
          }
          }
          /**
          * 對象持久化(Insert)測試方法
          *
          * JUnit中,以”test”作為前綴的方法為測試方法,將被JUnit自動添加
          * 到測試計劃中運行
          */
          public void testInsert(){
          try {
          TUser user = new TUser();
          user.setName("Emma");
          session.save(user);
          session.flush();
          } catch (HibernateException e) {
          e.printStackTrace();
          Assert.fail(e.getMessage());
          }
          }
          /**
          * 對象讀?。⊿elect)測試
          * 請保證運行之前數(shù)據(jù)庫中已經(jīng)存在name=’Erica’的記錄
          */
          public void testSelect(){
          String hql=
          " from TUser where name='Erica'";
          try {
          List userList = session.find(hql);
          TUser user =(TUser)userList.get(0);
          Assert.assertEquals(user.getName(),"Erica");
          } catch (HibernateException e) {
          e.printStackTrace();
          Assert.fail(e.getMessage());
          }
          }
          }
          主流IDE,如Eclipse、Intellij IDEA 和JBuilder 中都內(nèi)置了JUnit支持。下面是Eclipse
          中運行該代碼的結(jié)果(在Run菜單中選擇Run as -> JUnit Test即可):
          現(xiàn)在我們已經(jīng)成功實現(xiàn)了一個簡單的TUser 實例的保存和讀取??梢钥吹?,程序中通過
          少量代碼實現(xiàn)了Java 對象和數(shù)據(jù)庫數(shù)據(jù)的同步,同時借助Hibernate的有力支持,輕松實現(xiàn)
          了對象到關(guān)系型數(shù)據(jù)庫的映射。
          相對傳統(tǒng)的JDBC數(shù)據(jù)訪問模式,這樣的實現(xiàn)無疑更符合面向?qū)ο蟮乃枷?,同時也大大
          提高了開發(fā)效率。
          上面的代碼中引入了幾個Hibernate基礎(chǔ)語義:
          1. Configuration
          2. SessionFactory
          3. Session
          下面我們就這幾個關(guān)鍵概念進行探討。
          Hibernate基礎(chǔ)語義
          Configuration
          正如其名,Configuration 類負責(zé)管理Hibernate 的配置信息。Hibernate 運行時需要
          獲取一些底層實現(xiàn)的基本信息,其中幾個關(guān)鍵屬性包括:
          1. 數(shù)據(jù)庫URL
          2. 數(shù)據(jù)庫用戶
          3. 數(shù)據(jù)庫用戶密碼
          4. 數(shù)據(jù)庫JDBC驅(qū)動類
          5. 數(shù)據(jù)庫dialect,用于對特定數(shù)據(jù)庫提供支持,其中包含了針對特定數(shù)據(jù)庫特性
          的實現(xiàn),如Hibernate數(shù)據(jù)類型到特定數(shù)據(jù)庫數(shù)據(jù)類型的映射等。
          使用Hibernate 必須首先提供這些基礎(chǔ)信息以完成初始化工作,為后繼操作做好準
          備。這些屬性在hibernate配置文件(hibernate.cfg.xml 或hibernate.properties)中加以設(shè)
          定(參見前面“Hibernate配置”中的示例配置文件內(nèi)容)。
          當(dāng)我們調(diào)用:
          Configuration config = new Configuration().configure();
          時,Hibernate會自動在當(dāng)前的CLASSPATH 中搜尋hibernate.cfg.xml 文件并將其讀
          取到內(nèi)存中作為后繼操作的基礎(chǔ)配置。Configuration 類一般只有在獲取SessionFactory
          時需要涉及,當(dāng)獲取SessionFactory 之后,由于配置信息已經(jīng)由Hibernate 維護并綁定
          在返回的SessionFactory之上,因此一般情況下無需再對其進行操作。
          我們也可以指定配置文件名,如果不希望使用默認的hibernate.cfg.xml 文件作為配
          置文件的話:
          File file = new File("c:\\sample\\myhibernate.xml");
          Configuration config = new Configuration().configure(file);
          SessionFactory
          SessionFactory 負責(zé)創(chuàng)建Session 實例。我們可以通過Configuation 實例構(gòu)建
          SessionFactory:
          Configuration config = new Configuration().configure();
          SessionFactory sessionFactory = config.buildSessionFactory();
          Configuration實例config會根據(jù)當(dāng)前的配置信息,構(gòu)造SessionFactory實例并返回。
          SessionFactory 一旦構(gòu)造完畢,即被賦予特定的配置信息。也就是說,之后config 的任
          何變更將不會影響到已經(jīng)創(chuàng)建的SessionFactory 實例(sessionFactory)。如果需要
          使用基于改動后的config 實例的SessionFactory,需要從config 重新構(gòu)建一個
          SessionFactory實例。
          Session
          Session是持久層操作的基礎(chǔ),相當(dāng)于JDBC中的Connection。
          Session實例通過SessionFactory實例構(gòu)建:
          Configuration config = new Configuration().configure();
          SessionFactory sessionFactory = config.buildSessionFactory();
          Session session = sessionFactory.openSession();
          之后我們就可以調(diào)用Session所提供的save、find、flush等方法完成持久層操作:
          Find:
          String hql= " from TUser where name='Erica'";
          List userList = session.find(hql);
          Save:
          TUser user = new TUser();
          user.setName("Emma");
          session.save(user);
          session.flush();
          最后調(diào)用Session.flush方法強制數(shù)據(jù)庫同步,這里即強制Hibernate將user實
          例立即同步到數(shù)據(jù)庫中。如果在事務(wù)中則不需要flush方法,在事務(wù)提交的時候,
          hibernate自動會執(zhí)行flush方法,另外當(dāng)Session關(guān)閉時,也會自動執(zhí)行flush方法。
          Hibernate高級特性
          XDoclet 與Hibernate 映射
          在POJO 中融合XDoclet 的映射文件自動生成機制,提供了除手動編碼和由數(shù)據(jù)庫導(dǎo)出
          基礎(chǔ)代碼的第三種選擇。
          本章將結(jié)合XDoclet對Hibernate中的數(shù)據(jù)映射進行介紹。
          實際開發(fā)中,往往首先使用MiddleGen 和hbm2java 工具生成帶有XDoclet tag的POJO
          (MiddleGen build.xml中的genXDocletTags選項決定了是否在映射文件中生成XDoclet Tag,
          詳見Hibernate Quick Start章節(jié)中關(guān)于MiddleGen的說明)。之后通過修改POJO中的XDoclet
          tag進行映射關(guān)系調(diào)整。
          XDoclet已經(jīng)廣泛運用在EJB開發(fā)中,在其最新版本里,包含了一個為Hibernate提供支
          持的子類庫Hibernate Doclet,其中包含了生成Hibernate映射文件所需的ant構(gòu)建支持以及
          java doc tag支持。
          XDoclet實現(xiàn)基本原理是,通過在Java代碼加入特定的JavaDoc tag,從而為其添加特定
          的附加語義,之后通過XDoclet工具對代碼中JavaDoc Tag進行分析,自動生成與代碼對應(yīng)
          的配置文件,XDoclet。
          在Hibernate-Doclet中,通過引入Hibernate相關(guān)的JavaDoc tag,我們就可以由代碼生成
          對應(yīng)的Hibernate映射文件。
          下面是一個代碼片斷,演示了Hibernate-Doclet的使用方式:
          /**
          * @hibernate.class
          * table="TUser"
          */
          public class TUser implements Serializable {
          ……
          /**
          * @hibernate.property
          * column="name"
          * length="50"
          * not-null="true"
          *
          * @return String
          */
          public String getName() {
          return this.name;
          }
          ……
          }
          以上是使用Hibernate-Doclet 描述POJO(TUser)及其對應(yīng)表(TUser)之間映射關(guān)系
          的一個例子。
          其中用到了兩個hibernate doclet tag,@hibernate.class和@hibernate.property。
          這兩個tag分別描述了POJO所對應(yīng)的數(shù)據(jù)庫表信息,以及其字段對應(yīng)的庫表字段信息。
          之后Hibernate Doclet就會根據(jù)這些信息生成映射文件:
          <
          hibernate-mapping>
          <class
          name="net.xiaxin.xdoclet.TUser"
          table="TUser"
          >
          <property
          name="name"
          type="java.lang.String"
          column="name"
          not-null="true"
          length="50"
          >
          </class>
          </hibernate-mapping>
          這樣我們只需要維護Java 代碼,而無需再手動編寫具體的映射文件即可完成Hibernate
          基礎(chǔ)代碼。
          熟記Hibernate-Doclet 眾多的Tag,顯然不是件輕松的事情,好在目前的主流IDE 都提
          供了Live Template支持。我們只需進行一些配置工作,就可以實現(xiàn)Hibernate-Doclet Tag
          的自動補全功能,從而避免了手工編寫過程中可能出現(xiàn)的問題。
          附錄中提供了主流IDE,包括JBuilder,Intellij IDEA,Eclipse的Hibernate-Doclet集成
          指南。
          下面我們就Hibernate Doclet 中常用的Tag 進行探討,關(guān)于Tag 的詳細參考,請參見
          XDoclet 的官方指南(http://xdoclet.sourceforge.net/xdoclet/tags/hibernate-tags.html)以及
          Hibernate Reference(http://www.hibernate.org)。
          常用Hibernate-Doclet Tag介紹:
          1. Class 層面:
          1) @hibernate.class
          描述POJO 與數(shù)據(jù)庫表之間的映射關(guān)系,并指定相關(guān)的運行參數(shù)。
          參數(shù) 描述 類型 必須
          table 類對應(yīng)的表名
          默認值:當(dāng)前類名
          Text N
          dynamic-update 生成Update SQL時,僅包含發(fā)生變動
          的字段
          默認值: false
          Bool N
          dynamic-insert 生成Insert SQL時,僅包含非空(null)
          字段
          默認值:false
          Bool N
          Proxy 代理類
          默認值:空
          Text N
          discriminator-value 子類辨別標(biāo)識,用于多態(tài)支持。 Text N
          where 數(shù)據(jù)甄選條件,如果只需要處理庫表中某
          些特定數(shù)據(jù)的時候,可通過此選項設(shè)定結(jié)
          果集限定條件。
          如用戶表中保存了全國所有用戶的數(shù)據(jù),
          而我們的系統(tǒng)只是面向上海用戶,則可指
          定where=”location=’Shanghai’"
          Text N
          典型場景:
          /**
          * @hibernate.class
          * table="TUser" (1)
          * dynamic-update="true" (2)
          * dynamic-insert="true" (3)
          * proxy=”” (4)
          * discriminator-value=”1” (5)
          */
          public class TUser implements Serializable {
          ……
          }
          本例中:
          1 table參數(shù)指定了當(dāng)前類(TUser)對應(yīng)數(shù)據(jù)庫表“TUser”。
          2 dynamic-update 參數(shù)設(shè)定為生成Update SQL 時候,只包括當(dāng)前發(fā)生變化的
          字段(提高DB Update性能)。
          3 Dynamic-insert 參數(shù)設(shè)定為生成Insert SQL 時候,只包括當(dāng)前非空字段。
          (提高DB Insert性能)
          4 Proxy 參數(shù)為空,表明當(dāng)前類不使用代理(Proxy)。代理類的作用是為Lazy
          Loading提供支持,請參見下面關(guān)于Lazy Loading的有關(guān)內(nèi)容。
          5 discriminator-value參數(shù)設(shè)為”1”。
          discriminator-value 參數(shù)的目的是對多態(tài)提供支持。請參見下面關(guān)于
          @hibernate.discriminator的說明。
          2) @hibernate.discriminator
          @hibernate.discriminator(識別器) 用于提供多態(tài)支持。
          參數(shù) 描述 類型 必須
          column 用于區(qū)分各子類的字段名稱。
          默認值:當(dāng)前類名
          text Y
          type 對應(yīng)的Hibernate類型 Bool N
          length 字段長度 Bool N
          如:
          TUser類對應(yīng)數(shù)據(jù)庫表TUser,并且User類有兩個派生類SysAdmin、
          SysOperator。
          在TUser表中, 根據(jù)user_type字段區(qū)分用戶類型。
          為了讓Hibernate根據(jù)user_type能自動識別對應(yīng)的Class類型(如 user_type==1
          則自動映射到SysAdmin類,user_type==2 則自動映射到SysOperator類),我們需要
          在映射文件中進行配置,而在Hibernate-Doclet中,對應(yīng)的就是
          @hibernate.discriminator 標(biāo)識和 @hibernate.class 以及 @hibernate.subclass 的
          discriminator-value屬性。
          典型場景:
          /**
          *
          * @hibernate.class
          * table="TUser"
          * dynamic-update="true"
          * dynamic-insert="true"
          *
          * @hibernate.discriminator column="user_type" type="integer"
          */
          public class TUser implements Serializable {
          ……
          }
          根類TUser 中,通過@hibernate.discriminator 指定了以"user_type"字段
          作為識別字段。
          /**
          * @hibernate.subclass
          * discriminator-value="1"
          */
          public class SysAdmin extends TUser {
          ……
          }
          /**
          * @hibernate.subclass
          * discriminator-value="2"
          */
          public class SysOperator extends TUser {
          ……
          }
          SysAdmin 和SysOperator 均繼承自TUser,其discriminator-value 分別設(shè)置
          為"1"和"2",運行期Hibernate 在讀取t_user 表數(shù)據(jù)時,會根據(jù)其user_type 字段進行
          判斷,如果是1 的話則映射到SysAdmin類,如果是2 映射到SysOperator 類。
          上例中,描述SysAdmin 和SysOperator 時,我們引入了一個Tag:
          @hibernate.subclass,顧名思義,@hibernate.subclass與@hibernate.class
          不同之處就在于,@hibernate.subclass 描述的是一個子類,實際上,這兩個Tag
          除去名稱不同外,并沒有什么區(qū)別。
          2. Method層面:
          1) @hibernate.id
          描述POJO 中關(guān)鍵字段與數(shù)據(jù)庫表主鍵之間的映射關(guān)系。
          參數(shù) 描述 類型 必須
          column 主鍵字段名
          默認值:當(dāng)前類名
          Text N
          type 字段類型。
          Hibernate總是使用對象型數(shù)據(jù)類型作
          為字段類型,如int對應(yīng)Integer,因此
          這里將id設(shè)為基本類型[如int]以避免對
          象創(chuàng)建的開銷的思路是沒有實際意義的,
          即使這里設(shè)置為基本類型,Hibernate內(nèi)
          部還是會使用對象型數(shù)據(jù)對其進行處理,
          只是返回數(shù)據(jù)的時候再轉(zhuǎn)換為基本類型
          而已。
          Text N
          length 字段長度 Text N
          unsaved-value 用于對象是否已經(jīng)保存的判定值。
          詳見“數(shù)據(jù)訪問”章節(jié)的相關(guān)討論。
          Text N
          generator-class 主鍵產(chǎn)生方式(詳見Hibernate Quick
          Start中關(guān)于MiddleGen的相關(guān)說明)
          取值可為下列值中的任意一個:
          assigned
          hilo
          seqhilo
          increment
          identity
          sequence
          native
          uuid.hex
          uuid.string
          foreign
          Text Y
          2) @hibernate.property
          描述POJO 中屬性與數(shù)據(jù)庫表字段之間的映射關(guān)系。
          參數(shù) 描述 類型 必須
          column 數(shù)據(jù)庫表字段名
          默認值:當(dāng)前類名
          Text N
          type 字段類型 Text N
          length 字段長度 Text N
          not-null 字段是否允許為空 Bool N
          unique 字段是否唯一(是否允許重復(fù)值) Bool N
          insert Insert 操作時是否包含本字段數(shù)據(jù)
          默認:true
          Bool N
          update Update操作時是否包含本字段數(shù)據(jù)
          默認:true
          Bool N
          典型場景:
          /**
          * @hibernate.property
          * column="name"
          * length="50"
          * not-null="true"
          *
          * @return String
          */
          public String getName() {
          return this.name;
          }
          注意:在編寫代碼的時候請,對將POJO的getter/setter方法設(shè)定為public,如果
          設(shè)定為private,Hibernate將無法對屬性的存取進行優(yōu)化,只能轉(zhuǎn)而采用傳統(tǒng)的反射機制
          進行操作,這將導(dǎo)致大量的性能開銷(特別是在1.4之前的Sun JDK版本以及IBM JDK中,
          反射所帶來的系統(tǒng)開銷相當(dāng)可觀)。
          包含XDoclet Tag的代碼必須由xdoclet程序進行處理以生成對應(yīng)的映射文件,
          xdoclet的處理模塊可通過ant進行加載,下面是一個簡單的hibernate xdoclet的ant
          構(gòu)建腳本(注意實際使用時需要根據(jù)實際情況對路徑和CLASSPATH設(shè)定進行調(diào)整):
          <?xml version="1.0"?>
          <project name="Hibernate" default="hibernate" basedir=".">
          <property name="xdoclet.lib.home"
          value="C:\xdoclet-1.2.1\lib"/>
          <target name="hibernate" depends=""
          description="Generates Hibernate class descriptor files.">
          <taskdef name="hibernatedoclet"
          classname="xdoclet.modules.hibernate.HibernateDocletTask">
          <classpath>
          <fileset dir="${xdoclet.lib.home}">
          <include name="*.jar"/>
          </fileset>
          </classpath>
          </taskdef>
          <hibernatedoclet
          destdir="./src/"
          excludedtags="@version,@author,@todo"
          force="true"
          verbose="true"
          mergedir=".">
          <fileset dir="./src/">
          <include name="**/hibernate/sample/*.java"/>
          </fileset>
          <hibernate version="2.0"/>
          </hibernatedoclet>
          </target>
          </project>
          除了上面我們介紹的Hibernate Doclet Tag,其他還有:
          Class層面;
          @hibernate.cache
          @hibernate.jcs-cache
          @hibernate.joined-subclass
          @hibernate.joined-subclass-key
          @hibernate.query
          Method層面
          @hibernate.array
          @hibernate.bag
          @hibernate.collection-cache
          @hibernate.collection-composite-element
          @hibernate.collection-element
          @hibernate.collection-index
          @hibernate.collection-jcs-cache
          @hibernate.collection-key
          @hibernate.collection-key-column
          @hibernate.collection-many-to-many
          @hibernate.collection-one-to-many
          @hibernate.column
          @hibernate.component
          @hibernate.generator-param
          @hibernate.index-many-to-many
          @hibernate.list
          @hibernate.many-to-one
          @hibernate.map
          @hibernate.one-to-one
          @hibernate.primitive-array
          @hibernate.set
          @hibernate.timestamp
          @hibernate.version
          具體的Tag描述請參見XDoclet官方網(wǎng)站提供的Tag說明1。下面的Hibernate高級特性介
          紹中,我們也將涉及到這些Tag的實際使用。
          1 http://xdoclet.sourceforge.net/xdoclet/tags/hibernate-tags.html
          數(shù)據(jù)檢索
          數(shù)據(jù)查詢與檢索是Hibernate中的一個亮點。相對其他ORM實現(xiàn)而言,Hibernate
          提供了靈活多樣的查詢機制。其中包括:
          1. Criteria Query
          2. Hibernate Query Language (HQL)
          3. SQL
          Criteria Query
          Criteria Query通過面向?qū)ο蠡脑O(shè)計,將數(shù)據(jù)查詢條件封裝為一個對象。簡單來
          講,Criteria Query可以看作是傳統(tǒng)SQL的對象化表示,如:
          Criteria criteria = session.createCriteria(TUser.class);
          criteria.add(Expression.eq("name","Erica"));
          criteria.add(Expression.eq("sex",new Integer(1)));
          這里的criteria 實例實際上是SQL “Select * from t_user where
          name=’Erica’ and sex=1”的封裝(我們可以打開Hibernate 的show_sql 選項,
          以觀察Hibernate在運行期生成的SQL語句)。
          Hibernate 在運行期會根據(jù)Criteria 中指定的查詢條件(也就是上面代碼中通過
          criteria.add方法添加的查詢表達式)生成相應(yīng)的SQL語句。
          這種方式的特點是比較符合Java 程序員的編碼習(xí)慣,并且具備清晰的可讀性。正因
          為此,不少ORM實現(xiàn)中都提供了類似的實現(xiàn)機制(如Apache OJB)。
          對于Hibernate的初學(xué)者,特別是對SQL了解有限的程序員而言,Criteria Query
          無疑是上手的極佳途徑,相對HQL,Criteria Query提供了更易于理解的查詢手段,借
          助IDE的Coding Assist機制,Criteria的使用幾乎不用太多的學(xué)習(xí)。
          Criteria 查詢表達式
          Criteria 本身只是一個查詢?nèi)萜鳎唧w的查詢條件需要通過Criteria.add
          方法添加到Criteria實例中。
          如前例所示,Expression 對象具體描述了查詢條件。針對SQL 語法,
          Expression提供了對應(yīng)的查詢限定機制,包括:
          方法 描述
          Expression.eq 對應(yīng)SQL“field = value”表達式。
          如Expression.eq("name","Erica")
          Expression.allEq 參數(shù)為一個Map對象,其中包含了多個屬性-值對
          應(yīng)關(guān)系。相當(dāng)于多個Expression.eq關(guān)系的疊加。
          Expression.gt 對應(yīng)SQL中的 “field > value ” 表達式
          Expression.ge 對應(yīng)SQL中的 “field >= value” 表達式
          Expression.lt 對應(yīng)SQL中的 “field < value” 表達式
          Expression.le 對應(yīng)SQL中的 “field <= value” 表達式
          Expression.between 對應(yīng)SQL中的 “between” 表達式
          如下面的表達式表示年齡(age)位于13到50區(qū)
          間內(nèi)。
          Expression.between("age",new
          Integer(13),new Integer(50));
          Expression.like 對應(yīng)SQL中的 “field like value” 表達式
          Expression.in 對應(yīng)SQL中的 ”field in …” 表達式
          Expression.eqProperty 用于比較兩個屬性之間的值,對應(yīng)SQL中的“field
          = field”。
          如:
          Expression.eqProperty(
          "TUser.groupID",
          "TGroup.id"
          );
          Expression.gtProperty 用于比較兩個屬性之間的值,對應(yīng)SQL中的“field
          > field”。
          Expression.geProperty 用于比較兩個屬性之間的值,對應(yīng)SQL中的“field
          >= field”。
          Expression.ltProperty 用于比較兩個屬性之間的值,對應(yīng)SQL中的“field
          < field”。
          Expression.leProperty 用于比較兩個屬性之間的值,對應(yīng)SQL中的“field
          <= field”。
          Expression.and and關(guān)系組合。
          如:
          Expression.and(
          Expression.eq("name","Erica"),
          Expression.eq(
          "sex",
          new Integer(1)
          )
          );
          Expression.or or關(guān)系組合。
          如:
          Expression.or(
          Expression.eq("name","Erica"),
          Expression.eq("name","Emma")
          );
          Expression.sql 作為補充,本方法提供了原生SQL語法的支持。我
          們可以通過這個方法直接通過SQL語句限定查詢
          條件。
          下面的代碼返回所有名稱以“Erica”起始的記錄:
          Expression.sql(
          “l(fā)ower({alias}.name) like lower(?)”,
          "Erica%",
          Hibernate.STRING
          );
          其中的“{alias}”將由Hibernate在運行期使
          用當(dāng)前關(guān)聯(lián)的POJO別名替換。
          注意Expression 各方法中的屬性名參數(shù)(如Express.eq中的第一個參數(shù)),這里
          所謂屬性名是POJO中對應(yīng)實際庫表字段的屬性名(大小寫敏感),而非庫表中的實
          際字段名稱。
          Criteria 高級特性
          限定返回的記錄范圍
          通過criteria. setFirstResult/setMaxResults 方法可以限制一次查詢返回
          的記錄范圍:
          Criteria criteria = session.createCriteria(TUser.class);
          //限定查詢返回檢索結(jié)果中,從第一百條結(jié)果開始的20條記錄
          criteria.setFirstResult(100);
          criteria.setMaxResults(20);
          對查詢結(jié)果進行排序
          //查詢所有g(shù)roupId=2的記錄
          //并分別按照姓名(順序)和groupId(逆序)排序
          Criteria criteria = session.createCriteria(TUser.class);
          criteria.add(Expression.eq("groupId",new Integer(2)));
          criteria.addOrder(Order.asc("name"));
          criteria.addOrder(Order.desc("groupId"));
          Criteria作為一種對象化的查詢封裝模式,不過由于Hibernate在實現(xiàn)過程中將精力
          更加集中在HQL查詢語言上,因此Criteria的功能實現(xiàn)還沒做到盡善盡美(這點上,OJB
          的Criteria 實現(xiàn)倒是值得借鑒),因此,在實際開發(fā)中,建議還是采用Hibernate 官
          方推薦的查詢封裝模式:HQL。
          Hibernate Query Language (HQL)
          Criteria提供了更加符合面向?qū)ο缶幊棠J降牟樵兎庋b模式。不過,HQL(Hibernate
          Query Language)提供了更加強大的功能,在官方開發(fā)手冊中,也將HQL作為推薦的查詢
          模式。
          相對Criteria,HQL提供了更接近傳統(tǒng)SQL語句的查詢語法,也提供了更全面的特性。
          最簡單的一個例子:
          String hql = "from org.hibernate.sample.TUser";
          Query query = session.createQuery(hql);
          List userList = query.list();
          上面的代碼將取出TUser的所有對應(yīng)記錄。
          如果我們需要取出名為“Erica”的用戶的記錄,類似SQL,我們可以通過SQL 語句加
          以限定:
          String hql =
          "from org.hibernate.sample.TUser as user where user.name='Erica'";
          Query query = session.createQuery(hql);
          List userList = query.list();
          其中我們新引入了兩個子句“as”和“where”,as子句為類名創(chuàng)建了一個別名,而where
          子句指定了限定條件。
          HQL 子句本身大小寫無關(guān),但是其中出現(xiàn)的類名和屬性名必須注意大小寫區(qū)分。
          關(guān)于HQL,Hibernate 官方開發(fā)手冊中已經(jīng)提供了極其詳盡的說明和示例,詳見
          Hibernate官方開發(fā)手冊(Chapter 11)。
          數(shù)據(jù)關(guān)聯(lián)
          一對一關(guān)聯(lián)
          配置:
          Hibernate中的一對一關(guān)聯(lián)由“one-to-one”節(jié)點定義。
          在我們的權(quán)限管理系統(tǒng)示例中,每個用戶都從屬于一個用戶組。如用戶“Erica”
          從屬于“System Admin”組,從用戶的角度出發(fā),這就是一個典型的(單向)一對
          一關(guān)系。
          每個用戶對應(yīng)一個組,這在我們的系統(tǒng)中反映為TUser 到 TGroup 的
          one-to-one 關(guān)系。其中TUser 是主控方,TGroup是被動方。
          one-to-one關(guān)系定義比較簡單,只需在主控方加以定義。這里,我們的目標(biāo)是
          由TUser 對象獲取其對應(yīng)的TGroup 對象。因此TUser 對象是主控方,為了實現(xiàn)一
          對一關(guān)系,我們在TUser 對象的映射文件TUser.hbm.xml 中加入one-to-one節(jié)
          點,對TGroup對象進行一對一關(guān)聯(lián):
          <hibernate-mapping>
          <class
          name="org.hibernate.sample.TUser"
          table="t_user"
          dynamic-update="true"
          dynamic-insert="true"
          >
          ……
          <one-to-one
          name="group"
          class="org.hibernate.sample.TGroup"
          cascade="none"
          outer-join="auto"
          constrained="false"
          />
          ……
          </class>
          </hibernate-mapping>
          如果采用XDoclet,則對應(yīng)的Tag如下:
          /**
          * @hibernate.class
          * table="t_user"
          * dynamic-update="true"
          * dynamic-insert="true"
          *
          */
          public class TUser implements Serializable {
          ……
          private TGroup group;
          /**
          * @hibernate.one-to-one
          * name="group"
          * cascade="none"
          * class="org.hibernate.sample.TGroup"
          * outer-join="auto"
          * @return
          */
          public TGroup getGroup() {
          return group;
          }
          ……
          }
          one-to-one 節(jié)點有以下屬性:
          屬性 描述 類型 必須
          name 映射屬性 Text N
          class 目標(biāo)映射類。
          注意要設(shè)為包含Package name的全路
          徑名稱。
          Text N
          cascade 操作級聯(lián)(cascade)關(guān)系。
          可選值:
          all : 所有情況下均進行級聯(lián)操作。
          none:所有情況下均不進行級聯(lián)操作。
          save-update:在執(zhí)行save-update時
          進行級聯(lián)操作。
          delete:在執(zhí)行delete時進行級聯(lián)操作。
          級聯(lián)(cascade)在Hibernate映射關(guān)
          系中是個非常重要的概念。它指的是當(dāng)主
          控方執(zhí)行操作時,關(guān)聯(lián)對象(被動方)是
          否同步執(zhí)行同一操作。如對主控對象調(diào)用
          save-update或delete方法時,是否同
          時對關(guān)聯(lián)對象(被動方)進行
          Text N
          save-update或delete。
          這里,當(dāng)用戶(TUser)被更新或者刪除
          時,其所關(guān)聯(lián)的組(TGroup)不應(yīng)被修
          改或者刪除,因此,這里的級聯(lián)關(guān)系設(shè)置
          為none。
          constrained 約束
          表明主控表的主鍵上是否存在一個外鍵
          (foreign key)對其進行約束。這個選
          項關(guān)系到save、delete等方法的級聯(lián)操
          作順序。
          Bool N
          outer-join 是否使用外聯(lián)接。
          true:總是使用outer-join
          false:不使用outer-join
          auto(默認) :如果關(guān)聯(lián)對象沒有采用
          Proxy機制,則使用outer-join.
          Text N
          property-ref 關(guān)聯(lián)類中用于與主控類相關(guān)聯(lián)的屬性名
          稱。
          默認為關(guān)聯(lián)類的主鍵屬性名。
          這里我們通過主鍵達成一對一的關(guān)聯(lián),所
          以采用默認值即可。如果一對一的關(guān)聯(lián)并
          非建立在主鍵之間,則可通過此參數(shù)指定
          關(guān)聯(lián)屬性。
          Text N
          access 屬性值的讀取方式。
          可選項:
          field
          property(默認)
          ClassName
          Text N
          一對多關(guān)聯(lián)
          一對多關(guān)系在系統(tǒng)實現(xiàn)中也很常見。典型的例子就是父親與孩子的關(guān)系。 而在我
          們現(xiàn)在的這個示例中,每個用戶(TUser)都關(guān)聯(lián)到多個地址(TAddress),如一個
          用戶可能擁有辦公室地址、家庭地址等多個地址屬性。這樣,在系統(tǒng)中,就反應(yīng)為一
          個“一對多”關(guān)聯(lián)。
          一對多關(guān)系分為單向一對多關(guān)系和雙向一對多關(guān)系。
          單向一對多關(guān)系只需在“一”方進行配置,雙向一對多關(guān)系需要在關(guān)聯(lián)雙方均加
          以配置。
          ? 單向一對多關(guān)系
          配置:
          對于主控方(TUser):
          TUser.hbm.xml:
          <hibernate-mapping>
          <class
          name="org.hibernate.sample.TUser"
          table="t_user"
          dynamic-update="true"
          dynamic-insert="true"
          >
          ……
          <set
          name="addresses"
          table="t_address"
          lazy="false"
          inverse="false"
          cascade="all"
          sort="unsorted"
          order-by="zipcode asc"
          >
          <key
          column="user_id"
          >
          </key>
          <one-to-many
          class="org.hibernate.sample.TAddress"
          />
          </set>
          ……
          </class>
          </hibernate-mapping>
          對應(yīng)的XDoclet Tag 如下:
          /**
          * @hibernate.collection-one-to-many
          * class="org.hibernate.sample.TAddress"
          *
          * @hibernate.collection-key column="user_id"
          *
          * @hibernate.set
          * name="addresses"
          * table="t_address"
          * inverse="false"
          * cascade="all"
          * lazy="false"
          * sort=”unsorted”
          * order-by="zipcode asc"
          *
          */
          public Set getAddresses() {
          return addresses;
          }
          被動方(Taddress)的記錄由Hibernate 負責(zé)讀取,之后存放在主控方指定的
          Collection類型屬性中。
          對于one-to-many 關(guān)聯(lián)關(guān)系, 我們可以采用java.util.Set ( 或者
          net.sf.hibernate.collection.Bag)類型的Collection,表現(xiàn)在XML 映射文件
          中也就是<set>…</set>(或<bag>…</bag>)節(jié)點。關(guān)于Hibernate的Collection
          實現(xiàn),請參見Hibernate Reference.
          one-to-many 節(jié)點有以下屬性:
          屬性 描述 類型 必須
          name 映射屬性 Text Y
          table 目標(biāo)關(guān)聯(lián)數(shù)據(jù)庫表。 Text Y
          lazy 是否采用延遲加載。
          關(guān)于延遲加載,請參見后面相關(guān)章節(jié)。
          Text N
          inverse 用于標(biāo)識雙向關(guān)聯(lián)中的被動方一端。
          inverse=false的一方(主控方)負責(zé)
          維護關(guān)聯(lián)關(guān)系。
          默認值: false
          Bool N
          cascade 操作級聯(lián)(cascade)關(guān)系。
          可選值:
          all : 所有情況下均進行級聯(lián)操作。
          none:所有情況下均不進行級聯(lián)操作。
          save-update:在執(zhí)行save-update時
          進行級聯(lián)操作。
          delete:在執(zhí)行delete時進行級聯(lián)操作。
          Text N
          sort 排序類型。 Text N
          可選值:
          unsorted :不排序(默認)
          natural :自然順序(避免與order-by
          搭配使用)
          comparatorClass :指以某個實現(xiàn)了
          java.util.Comparator接口的類作為排
          序算法。
          order-by 指定排序字段及其排序方式。
          (JDK1.4以上版本有效)。
          對應(yīng)SQL中的order by子句。
          避免與sort 的 “natural”模式同時使
          用。
          Text N
          where 數(shù)據(jù)甄選條件,如果只需要處理庫表中某
          些特定數(shù)據(jù)的時候,可通過此選項設(shè)定結(jié)
          果集限定條件。
          Text N
          outer-join 是否使用外聯(lián)接。
          true:總是使用outer-join
          false:不使用outer-join
          auto(默認) :如果關(guān)聯(lián)對象沒有采用
          Proxy機制,則使用outer-join.
          Text N
          batch-size 采用延遲加載特性時(Lazy Loading)
          一次讀入的數(shù)據(jù)數(shù)量。
          此處未采用延遲加載機制,因此此屬性忽
          略。
          Int N
          access 屬性值的讀取方式。
          可選項:
          field
          property(默認)
          ClassName
          Text N
          通過單向一對多關(guān)系進行關(guān)聯(lián)相對簡單,但是存在一個問題。由于是單向關(guān)聯(lián),
          為了保持關(guān)聯(lián)關(guān)系,我們只能通過主控方對被動方進行級聯(lián)更新。且如果被關(guān)聯(lián)方的
          關(guān)聯(lián)字段為“NOT NULL”,當(dāng)Hibernate創(chuàng)建或者更新關(guān)聯(lián)關(guān)系時,還可能出現(xiàn)約
          束違例。
          例如我們想為一個已有的用戶“Erica”添加一個地址對象:
          Transaction tx = session.beginTransaction();
          TAddress addr = new TAddress();
          addr.setTel("1123");
          addr.setZipcode("233123");
          addr.setAddress("Hongkong");
          user.getAddresses().add(addr);
          session.save(user);//通過主控對象級聯(lián)更新
          tx.commit();
          為了完成這個操作,Hibernate會分兩步(兩條SQL)來完成新增t_address記錄的操作:
          1. save(user)時:
          insert into t_address (user_id, address, zipcode, tel)
          values (null, "Hongkong", "233123", "1123")
          2. tx.commit()時
          update t_address set user_id=”1”, address="Hongkong",
          zipcode="233123", tel="1123" where id=2
          第一條SQL用于插入新的地址記錄。
          第二條SQL用于更新t_address,將user_id設(shè)置為其關(guān)聯(lián)的user對象的id值。
          問題就出在這里,數(shù)據(jù)庫中,我們的t_address.user_id字段為“NOT NULL”
          型,當(dāng)Hibernate執(zhí)行第一條語句創(chuàng)建t_address記錄時,試圖將user_id字段的
          值設(shè)為null,于是引發(fā)了一個約束違例異常:
          net.sf.hibernate.PropertyValueException: not-null property
          references a null or transient value:
          org.hibernate.sample.TAddress.userId
          因為關(guān)聯(lián)方向是單向,關(guān)聯(lián)關(guān)系由TUser對象維持,而被關(guān)聯(lián)的addr對象本身并
          不知道自己與哪個TUser對象相關(guān)聯(lián),也就是說,addr對象本身并不知道user_id應(yīng)
          該設(shè)為什么數(shù)值。
          因此,在保存addr時,只能先在關(guān)聯(lián)字段插入一個空值。之后,再由TUser對象
          將自身的id值賦予關(guān)聯(lián)字段addr.user_id,這個賦值操作導(dǎo)致addr對象屬性發(fā)生變
          動,在事務(wù)提交時,hibernate會發(fā)現(xiàn)這一改變,并通過update sql將變動后的數(shù)
          據(jù)保存到數(shù)據(jù)庫。
          第一個步驟中,企圖向數(shù)據(jù)庫的非空字段插入空值,因此導(dǎo)致了約束違例。
          既然TUser對象是主控方,為什么就不能自動先設(shè)置好下面的TAddress對象的
          關(guān)倆字段值再一次做Insert操作呢?莫名其妙?Ha,don’t ask me ,go to ask
          Hibernate TeamJ。
          我們可以在設(shè)計的時候通過一些手段進行調(diào)整,以避免這樣的約束違例,如將關(guān)
          聯(lián)字段設(shè)為允許NULL值、直接采用數(shù)值型字段作為關(guān)聯(lián)(有的時候這樣的調(diào)整并不可
          行,很多情況下我們必須針對現(xiàn)有數(shù)據(jù)庫結(jié)構(gòu)進行開發(fā)),或者手動為關(guān)聯(lián)字段屬性
          賦一個任意非空值(即使在這里通過手工設(shè)置了正確的user_id也沒有意義,
          hibernate還是會自動再調(diào)用一條Update語句進行更新)。
          甚至我們可以將被動方的關(guān)聯(lián)字段從其映射文件中剔除(如將user_id字段的映
          射從TAddress.hbm.xml中剔除)。這樣Hibernate在生成第一條insert語句的時
          候就不會包含這個字段(數(shù)據(jù)庫會使用字段默認值填充),如:之后update語句會根
          據(jù)主控方的one-to-many映射配置中的關(guān)聯(lián)字段去更新被動方關(guān)聯(lián)字段的內(nèi)容。在我
          們這里的例子中,如果將user_id字段從TAddress.hbm.xml文件中剔除,
          Hibernate在保存數(shù)據(jù)時會生成下面幾條SQL:
          1. insert into t_address (address, zipcode, tel) values
          ('Hongkong', '233123', '1123')
          2. update t_address set user_id=1 where id=7
          生成第一條insert語句時,沒有包含user_id字段,數(shù)據(jù)庫會使用該字段的默
          認值(如果有的話)進行填充。因此不會引發(fā)約束違例。之后,根據(jù)第一條語句返回
          的記錄id,再通過update語句對user_id字段進行更新。
          但是,縱使采用這些權(quán)益之計,由于Hibernate實現(xiàn)機制中,采用了兩條SQL進
          行一次數(shù)據(jù)插入操作,相對單條insert,幾乎是兩倍的性能開銷,效率較低,因此,
          對于性能敏感的系統(tǒng)而言,這樣的解決方案所帶來的開銷可能難以承受。
          針對上面的情況,我們想到,如果addr對象知道如何獲取user_id字段的內(nèi)容,
          那么執(zhí)行insert語句的時候直接將數(shù)據(jù)植入即可。這樣不但繞開了約束違例的可能,
          而且還節(jié)省了一條Update語句的開銷,大幅度提高了性能。
          雙向一對多關(guān)系的出現(xiàn)則解決了這個問題。它除了避免約束違例和提高性能的好
          處之外,還帶來另外一個優(yōu)點,由于建立了雙向關(guān)聯(lián),我們可以在關(guān)聯(lián)雙方中任意一
          方,訪問關(guān)聯(lián)的另一方(如可以通過TAddress對象直接訪問其關(guān)聯(lián)的TUser對象),
          這提供了更豐富靈活的控制手段。
          ? 雙向一對多關(guān)系
          雙向一對多關(guān)系,實際上是“單向一對多關(guān)系”與“多對一關(guān)系”的組合。也就
          是說我們必須在主控方配置單向一對多關(guān)系的基礎(chǔ)上,在被控方配置多對一關(guān)系與其
          對應(yīng)。
          配置:
          上面我們已經(jīng)大致完成了單向方一對多關(guān)系的配置,我們只需在此基礎(chǔ)上稍做修
          改,并對(t_address)的相關(guān)屬性進行配置即可:
          TUser.hbm.xml:
          <hibernate-mapping>
          <class
          name="org.hibernate.sample.TUser"
          table="t_user"
          dynamic-update="true"
          dynamic-insert="true"
          >
          ……
          <set
          name="addresses"
          table="t_address"
          lazy="false"
          inverse="true" ①
          cascade="all"
          sort="unsorted"
          order-by="zipcode asc"
          >
          <key
          column="user_id"
          >
          </key>
          <one-to-many
          class="org.hibernate.sample.TAddress"
          />
          </set>
          </class>
          </hibernate-mapping>
          ① 這里與前面不同,inverse被設(shè)為“true”,這意味著TUser不再作為主控方,
          而是將關(guān)聯(lián)關(guān)系的維護工作交給關(guān)聯(lián)對象org.hibernate.sample.TAddress 來
          完成。這樣TAddress對象在持久化過程中,就可以主動獲取其關(guān)聯(lián)的TUser對象的id,
          并將其作為自己的user_id,之后執(zhí)行一次insert操作即可完成全部工作。
          在one-to-many 關(guān)系中,將many 一方設(shè)為主動方(inverse=false)將有助性能
          的改善。(現(xiàn)實中也一樣,如果要讓胡錦濤記住全國人民的名字,估計花個幾十年也
          不可能,但要讓全國人民知道胡錦濤,可就不需要那么多時間了。J)
          對應(yīng)的 xdoclet tag 如下:
          public class TUser implements Serializable {
          ……
          private Set addresses = new HashSet();
          ……
          /**
          * @hibernate.collection-one-to-many
          * class="org.hibernate.sample.TAddress"
          *
          * @hibernate.collection-key column="user_id"
          *
          * @hibernate.set
          * name="addresses"
          * table="t_address"
          * inverse="true"
          * lazy="false"
          * cascade=”all”
          * sort="unsorted"
          * order-by="zipcode asc"
          */
          public Set getAddresses() {
          return addresses;
          }
          ……
          }
          TAddress.hbm.xml:
          <hibernate-mapping>
          <class
          name="org.hibernate.sample.TAddress"
          table="t_address"
          dynamic-update="false"
          dynamic-insert="false"
          >
          ……
          <many-to-one
          name="user" ①
          class="org.hibernate.sample.TUser"
          cascade="none"
          outer-join="auto"
          update="true"
          insert="true"
          access="property"
          column="user_id"
          not-null="true"
          />
          </class>
          </hibernate-mapping>
          ① 在TAddress 對象中新增一個TUser field “user”,并為其添加對應(yīng)的
          getter/setter 方法。同時刪除原有的user_id 屬性及其映射配置,否則運行期會報
          字段重復(fù)映射錯誤:“Repeated column in mapping”。
          對應(yīng)Xdoclet tag:
          public class TAddress implements Serializable {
          ……
          private TUser user;
          ……
          /**
          * @hibernate.many-to-one
          * name="user"
          * column="user_id"
          * not-null="true"
          *
          */
          public TUser getUser() {
          return this.user;
          }
          ……
          }
          再看上面那段代碼片斷:
          Criteria criteria = session.createCriteria(TUser.class);
          criteria.add(Expression.eq("name","Erica"));
          List userList = criteria.list();
          TUser user =(TUser)userList.get(0);
          Transaction tx = session.beginTransaction();
          TAddress addr = new TAddress();
          addr.setTel("1123");
          addr.setZipcode("233123");
          addr.setAddress("Hongkong");
          user.getAddresses().add(addr);
          session.save(user);//通過主控對象級聯(lián)更新
          tx.commit();
          嘗試運行這段代碼,結(jié)果凄涼的很,還是約束違例。
          為什么會這樣,我們已經(jīng)配置了TAddress的many-to-one關(guān)系,這么看來似
          乎沒什么效果……
          不過,別忘了上面提到的inverse 屬性,這里我們把TUser 的inverse 設(shè)為
          “true”,即指定由對方維護關(guān)聯(lián)關(guān)系,在這里也就是由TAddress維護關(guān)聯(lián)關(guān)系。
          TUser既然不再維護關(guān)聯(lián)關(guān)系,那么TAddress的user_id屬性它也自然不會關(guān)心,
          必須由TAddress自己去維護user_id:
          ……
          TAddress addr = new TAddress();
          addr.setTel("1123");
          addr.setZipcode("233123");
          addr.setAddress("Hongkong");
          addr.setUser(user);//設(shè)置關(guān)聯(lián)的TUser對象
          user.getAddresses().add(addr);
          session.save(user);//級聯(lián)更新
          ……
          觀察Hibernate執(zhí)行過程中調(diào)用的SQL語句:
          insert into t_address (user_id, address, zipcode, tel) values
          (1, 'Hongkong', '233123', '1123')
          正如我們所期望的,保存工作通過單條Insert語句的執(zhí)行來完成。
          many-to-one 節(jié)點有以下屬性:
          屬性 描述 類型 必須
          name 映射屬性 Text Y
          column 關(guān)聯(lián)字段。 Text N
          class 類名
          默認為映射屬性所屬類型
          Text N
          cascade 操作級聯(lián)(cascade)關(guān)系。
          可選值:
          all : 所有情況下均進行級聯(lián)操作。
          none:所有情況下均不進行級聯(lián)操作。
          save-update:在執(zhí)行save-update時
          進行級聯(lián)操作。
          delete:在執(zhí)行delete時進行級聯(lián)操作。
          Text N
          update 是否對關(guān)聯(lián)字段進行Update操作 Bool N
          默認:true
          insert 是否對關(guān)聯(lián)字段進行Insert操作
          默認:true
          Bool N
          outer-join 是否使用外聯(lián)接。
          true:總是使用outer-join
          false:不使用outer-join
          auto(默認) :如果關(guān)聯(lián)對象沒有采用
          Proxy機制,則使用outer-join.
          Text N
          property-ref 用于與主控類相關(guān)聯(lián)的屬性的名稱。
          默認為關(guān)聯(lián)類的主鍵屬性名。
          這里我們通過主鍵進行關(guān)聯(lián),所以采用默
          認值即可。如果關(guān)聯(lián)并非建立在主鍵之
          間,則可通過此參數(shù)指定關(guān)聯(lián)屬性。
          Text N
          access 屬性值的讀取方式。
          可選項:
          field
          property(默認)
          ClassName
          Text N
          級聯(lián)與關(guān)聯(lián)關(guān)系的差別?
          多對多關(guān)聯(lián)
          Hibernate關(guān)聯(lián)關(guān)系中相對比較特殊的就是多對多關(guān)聯(lián),多對多關(guān)聯(lián)與一對一關(guān)
          聯(lián)和一對多關(guān)聯(lián)不同,多對多關(guān)聯(lián)需要另外一張映射表用于保存多對多映射信息。
          由于多對多關(guān)聯(lián)的性能不佳(由于引入了中間表,一次讀取操作需要反復(fù)數(shù)次查
          詢),因此在設(shè)計中應(yīng)該避免大量使用。同時,在對多對關(guān)系中,應(yīng)根據(jù)情況,采取
          延遲加載(Lazy Loading 參見后續(xù)章節(jié))機制來避免無謂的性能開銷。
          在一個權(quán)限管理系統(tǒng)中,一個常見的多對多的映射關(guān)系就是Group 與Role,以
          及Role與Privilege之間的映射。
          ? Group代表“組”(如“業(yè)務(wù)主管”);
          ? Role代表“角色”(如“出納”、“財務(wù)”);
          ? Privilege 代表某個特定資源的訪問權(quán)限(如“修改財務(wù)報表”,“查詢
          財務(wù)報表”)。
          這里我們以Group和Role之間的映射為例:
          ? 一個Group中包含了多個Role,如某個“業(yè)務(wù)主管”擁有“出納”和“財
          務(wù)”的雙重角色。
          ? 而一個Role也可以屬于不同的Group。
          配置:
          在我們的實例中,TRole 和TPrivilege 對應(yīng)數(shù)據(jù)庫中的t_role、
          t_privilege表。
          TGroup.hbm.xml中關(guān)于多對多關(guān)聯(lián)的配置片斷:
          <hibernate-mapping>
          <class
          name="org.hibernate.sample.TGroup"
          table="t_group"
          dynamic-update="false"
          dynamic-insert="false"
          >
          ……
          <set
          name="roles"
          table="t_group_role" ①
          lazy="false"
          inverse="false"
          cascade="save-update" ②
          >
          <key
          column="group_id" ③
          >
          </key>
          <many-to-many
          class="org.hibernate.sample.TRole"
          column="role_id" ④
          />
          </set>
          </class>
          </hibernate-mapping>
          ① 這里為t_group 和t_role之間的映射表。
          ② 一般情況下,cascade應(yīng)該設(shè)置為“save-update”,對于多對多邏輯
          而言,很少出現(xiàn)刪除一方需要級聯(lián)刪除所有關(guān)聯(lián)數(shù)據(jù)的情況,如刪除一
          個Group,一般不會刪除其中包含的Role(這些Role 可能還被其他的
          Group所引用)。反之刪除Role一般也不會刪除其所關(guān)聯(lián)的所有Group。
          ③ 映射表中對于t_group表記錄的標(biāo)識字段。
          ④ 映射表中對于t_role表記錄的標(biāo)識字段。
          對應(yīng)的xdoclet tag如下:
          public class TGroup implements Serializable {
          ……
          private Set roles = new HashSet();
          /**
          * @hibernate.set
          * name="roles"
          * table="t_group_role"
          * lazy="false"
          * inverse="false"
          * cascade="save-update"
          * sort=”unsorted”
          *
          * @hibernate.collection-key
          * column="group_id"
          *
          * @hibernate.collection-many-to-many
          * class="org.hibernate.sample.TRole"
          * column="role_id"
          *
          */
          public Set getRoles() {
          return roles;
          }
          ……
          }
          TRole.hbm.xml中關(guān)于多對多關(guān)聯(lián)的配置片斷:
          <hibernate-mapping>
          <class
          name="org.hibernate.sample.TRole"
          table="t_role"
          dynamic-update="false"
          dynamic-insert="false"
          >
          ……
          <set
          name="groups"
          table="t_group_role"
          lazy="false"
          inverse="true"
          cascade="save-update"
          sort="unsorted"
          >
          <key
          column="role_id"
          >
          </key>
          <many-to-many
          class="org.hibernate.sample.TGroup"
          column="group_id"
          outer-join="auto"
          />
          </set>
          </class>
          </hibernate-mapping>
          對應(yīng)的xdoclet如下:
          public class TRole implements Serializable {
          private Set groups = new HashSet();
          ……
          /**
          *
          * @hibernate.set
          * name="groups"
          * table="t_group_role"
          * cascade="save-update"
          * inverse="true"
          * lazy="false"
          *
          * @hibernate.collection-key
          * column="role_id"
          *
          * @hibernate.collection-many-to-many
          * class="org.hibernate.sample.TGroup"
          * column="group_id"
          *
          *
          */
          public Set getGroups() {
          return groups;
          }
          }
          many-to-many節(jié)點中各個屬性描述:
          屬性 描述 類型 必須
          column 中間映射表中,關(guān)聯(lián)目標(biāo)表的關(guān)聯(lián)字段。 Text Y
          class 類名
          關(guān)聯(lián)目標(biāo)類。
          Text Y
          outer-join 是否使用外聯(lián)接。
          true:總是使用outer-join
          false:不使用outer-join
          auto(默認) :如果關(guān)聯(lián)對象沒有采用
          Proxy機制,則使用outer-join.
          Text N
          使用:
          多對多關(guān)系中,由于關(guān)聯(lián)關(guān)系是兩張表相互引用,因此在保存關(guān)聯(lián)狀態(tài)時必須對
          雙方同時保存。
          public void testPersist(){
          TRole role1 = new TRole();
          role1.setName("Role1");
          TRole role2 = new TRole();
          role2.setName("Role2");
          TRole role3 = new TRole();
          role3.setName("Role3");
          TGroup group1 = new TGroup();
          group1.setName("group1");
          TGroup group2 = new TGroup();
          group2.setName("group2");
          TGroup group3 = new TGroup();
          group3.setName("group3");
          group1.getRoles().add(role1);
          group1.getRoles().add(role2);
          group2.getRoles().add(role2);
          group2.getRoles().add(role3);
          group3.getRoles().add(role1);
          group3.getRoles().add(role3);
          role1.getGroups().add(group1);
          role1.getGroups().add(group3);
          role2.getGroups().add(group1);
          role2.getGroups().add(group2);
          role3.getGroups().add(group2);
          role3.getGroups().add(group3);
          try {
          Transaction tx = session.beginTransaction();
          //多對多關(guān)系必須同時對關(guān)聯(lián)雙方進行保存
          session.save(role1);
          session.save(role2);
          session.save(role3);
          session.save(group1);
          session.save(group2);
          session.save(group3);
          tx.commit();
          } catch (Exception e) {
          e.printStackTrace();
          Assert.fail(e.getMessage());
          }
          }
          上面的代碼創(chuàng)建3個TGroup對象和3個TRole對象,并形成了多對多關(guān)系。
          數(shù)據(jù)訪問
          PO和VO
          PO即 Persistence Object
          VO即 Value Object
          PO和VO是Hibernate中兩個比較關(guān)鍵的概念。
          首先,何謂VO,很簡單,VO就是一個簡單的值對象。
          如:
          TUser user = new TUser();
          user.setName("Emma");
          這里的user就是一個VO。VO只是簡單攜帶了對象的一些屬性信息。
          何謂PO? 即納入Hibernate管理框架中的VO??聪旅鎯蓚€例子:
          TUser user = new TUser();
          TUser anotherUser = new TUser();
          user.setName("Emma");
          anotherUser.setName("Kevin");
          //此時user和anotherUser都是VO
          Transaction tx = session.beginTransaction();
          session.save(user);
          //此時的user已經(jīng)經(jīng)過Hibernate的處理,成為一個PO
          //而anotherUser仍然是個VO
          tx.commit();
          //事務(wù)提交之后,庫表中已經(jīng)插入一條用戶”Emma”的記錄
          //對于anotherUser則無任何操作
          Transaction tx = session.beginTransaction();
          user.setName("Emma_1"); //PO
          anotherUser.setName("Kevin_1");//VO
          tx.commit();
          //事務(wù)提交之后,PO的狀態(tài)被固化到數(shù)據(jù)庫中
          //也就是說數(shù)據(jù)庫中“Emma”的用戶記錄已經(jīng)被更新為“Emma_1”
          //此時anotherUser仍然是個普通Java對象,它的屬性更改不會
          //對數(shù)據(jù)庫產(chǎn)生任何影響
          另外,通過Hibernate返回的對象也是PO:
          //由Hibernate返回的PO
          TUser user = (TUser)session.load(TUser.class,new Integer(1));
          VO經(jīng)過Hibernate進行處理,就變成了PO。
          上面的示例代碼session.save(user)中,我們把一個VO “user”傳遞給
          Hibernate的Session.save方法進行保存。在save方法中,Hibernate對其進
          行如下處理:
          1. 在當(dāng)前session所對應(yīng)的實體容器(Entity Map)中查詢是否存在user對象
          的引用。
          2. 如果引用存在,則直接返回user對象id,save過程結(jié)束.
          Hibernate中,針對每個Session有一個實體容器(實際上是一個Map對象),
          如果此容器中已經(jīng)保存了目標(biāo)對象的引用,那么hibernate會認為此對象已經(jīng)
          與Session相關(guān)聯(lián)。
          對于save操作而言,如果對象已經(jīng)與Session相關(guān)聯(lián)(即已經(jīng)被加入Session
          的實體容器中),則無需進行具體的操作。因為之后的Session.flush過程中,
          Hibernate會對此實體容器中的對象進行遍歷,查找出發(fā)生變化的實體,生成
          并執(zhí)行相應(yīng)的update語句。
          3. 如果引用不存在,則根據(jù)映射關(guān)系,執(zhí)行insert操作。
          a) 在我們這里的示例中,采用了native的id生成機制,因此hibernate會
          從數(shù)據(jù)庫取得insert操作生成的id并賦予user對象的id屬性。
          b) 將user對象的引用納入Hibernate的實體容器。
          c) save過程結(jié)束,返回對象id.
          而Session.load方法中,再返回對象之前,Hibernate就已經(jīng)將此對象納入其實
          體容器中。
          VO和PO的主要區(qū)別在于:
          ? VO是獨立的Java Object。
          ? PO是由Hibernate納入其實體容器(Entity Map)的對象,它代表了與數(shù)
          據(jù)庫中某條記錄對應(yīng)的Hibernate實體,PO的變化在事務(wù)提交時將反應(yīng)到實
          際數(shù)據(jù)庫中。
          如果一個PO與Session對應(yīng)的實體容器中分離(如Session關(guān)閉后的PO),那么
          此時,它又會變成一個VO。
          由PO、VO的概念,又引申出一些系統(tǒng)層次設(shè)計方面的問題。如在傳統(tǒng)的MVC架構(gòu)中,
          位于Model層的PO,是否允許被傳遞到其他層面。由于PO的更新最終將被映射到實
          際數(shù)據(jù)庫中,如果PO在其他層面(如View層)發(fā)生了變動,那么可能會對Model
          層造成意想不到的破壞。
          因此,一般而言,應(yīng)該避免直接PO傳遞到系統(tǒng)中的其他層面,一種解決辦法是,通
          過一個VO,通過屬性復(fù)制使其具備與PO相同屬性值,并以其為傳輸媒質(zhì)(實際上,
          這個VO被用作Data Transfer Object,即所謂的DTO),將此VO傳遞給其他層
          面以實現(xiàn)必須的數(shù)據(jù)傳送。
          屬性復(fù)制可以通過Apache Jakarta Commons Beanutils
          http://jakarta.apache.org/commons/beanutils/)組件提供的屬性批
          量復(fù)制功能,避免繁復(fù)的get/set操作。
          下面的例子中,我們把user對象的所有屬性復(fù)制到anotherUser對象中:
          TUser user = new TUser();
          TUser anotherUser = new TUser();
          user.setName("Emma");
          user.setUserType(1);
          try {
          BeanUtils.copyProperties(anotherUser,user);
          System.out.println("UserName => "
          +anotherUser.getName()
          );
          System.out.println("UserType => "
          + anotherUser.getUserType()
          );
          } catch (IllegalAccessException e) {
          e.printStackTrace();
          } catch (InvocationTargetException e) {
          e.printStackTrace();
          }
          關(guān)于unsaved-value
          在非顯示數(shù)據(jù)保存時,Hibernate將根據(jù)這個值來判斷對象是否需要保存。
          所謂顯式保存,是指代碼中明確調(diào)用session 的save、update、saveOrupdate方
          法對對象進行持久化。如:
          session.save(user);
          而在某些情況下,如映射關(guān)系中,Hibernate 根據(jù)級聯(lián)(Cascade)關(guān)系對聯(lián)接類進
          行保存。此時代碼中沒有針對級聯(lián)對象的顯示保存語句,需要Hibernate 根據(jù)對象當(dāng)前狀
          態(tài)判斷是否需要保存到數(shù)據(jù)庫。此時,Hibernate即將根據(jù)unsaved-value進行判定。
          首先Hibernate會取出目標(biāo)對象的id。
          之后,將此值與unsaved-value進行比對,如果相等,則認為目標(biāo)對象尚未保存,否
          則,認為對象已經(jīng)保存,無需再進行保存操作。
          如:user對象是之前由hibernate從數(shù)據(jù)庫中獲取,同時,此user對象的若干個關(guān)
          聯(lián)對象address 也被加載,此時我們向user 對象新增一個address 對象,此時調(diào)用
          session.save(user),hibernate會根據(jù)unsaved-value判斷user對象的數(shù)個address
          關(guān)聯(lián)對象中,哪些需要執(zhí)行save操作,而哪些不需要。
          對于我們新加入的address 對象而言,由于其id(Integer 型)尚未賦值,因此為
          null,與我們設(shè)定的unsaved-value(null)相同,因此hibernate將其視為一個未保存
          對象,將為其生成insert語句并執(zhí)行。
          這里可能會產(chǎn)生一個疑問,如果“原有”關(guān)聯(lián)對象發(fā)生變動(如user的某個“原有”
          的address對象的屬性發(fā)生了變化,所謂“原有”即此address對象已經(jīng)與user相關(guān)聯(lián),
          而不是我們在此過程中為之新增的),此時id值是從數(shù)據(jù)庫中讀出,并沒有發(fā)生改變,自然
          與unsaved-value(null)也不一樣,那么Hibernate是不是就不保存了?
          上面關(guān)于PO、VO 的討論中曾經(jīng)涉及到數(shù)據(jù)保存的問題,實際上,這里的“保存”,
          實際上是“insert”的概念,只是針對新關(guān)聯(lián)對象的加入,而非數(shù)據(jù)庫中原有關(guān)聯(lián)對象的
          “update”。所謂新關(guān)聯(lián)對象,一般情況下可以理解為未與Session 發(fā)生關(guān)聯(lián)的VO。而
          “原有”關(guān)聯(lián)對象,則是PO。如上面關(guān)于PO、VO的討論中所述:
          對于save操作而言,如果對象已經(jīng)與Session相關(guān)聯(lián)(即已經(jīng)被加入Session的實體
          容器中),則無需進行具體的操作。因為之后的Session.flush過程中,Hibernate
          會對此實體容器中的對象進行遍歷,查找出發(fā)生變化的實體,生成并執(zhí)行相應(yīng)的update
          語句。
          Inverse和Cascade
          Inverse,直譯為“反轉(zhuǎn)”。在Hibernate語義中,Inverse指定了關(guān)聯(lián)關(guān)系中的
          方向。
          關(guān)聯(lián)關(guān)系中,inverse=”false”的為主動方,由主動方負責(zé)維護關(guān)聯(lián)關(guān)系。具體可
          參見一對多關(guān)系中的描述。
          而Cascade,譯為“級聯(lián)”,表明對象的級聯(lián)關(guān)系,如TUser的Cascade設(shè)為all,
          就表明如果發(fā)生對user對象的操作,需要對user所關(guān)聯(lián)的對象也進行同樣的操作。如對
          user對象執(zhí)行save操作,則必須對user對象相關(guān)聯(lián)的address也執(zhí)行save操作。
          初學(xué)者常?;煜齣nverse和cascade,實際上,這是兩個互不相關(guān)的概念。Inverse
          指的是關(guān)聯(lián)關(guān)系的控制方向,而cascade指的是層級之間的連鎖操作。
          延遲加載(Lazy Loading)
          為了避免一些情況下,關(guān)聯(lián)關(guān)系所帶來的無謂的性能開銷。Hibernate引入了延遲加載的
          概念。
          如,示例中user對象在加載的時候,會同時讀取其所關(guān)聯(lián)的多個地址(address)對象,
          對于需要對address進行操作的應(yīng)用邏輯而言,關(guān)聯(lián)數(shù)據(jù)的自動加載機制的確非常有效。
          但是,如果我們只是想要獲得user的性別(sex)屬性,而不關(guān)心user的地址(address)
          信息,那么自動加載address的特性就顯得多余,并且造成了極大的性能浪費。為了獲得user
          的性別屬性,我們可能還要同時從數(shù)據(jù)庫中讀取數(shù)條無用的地址數(shù)據(jù),這導(dǎo)致了大量無謂的系統(tǒng)
          開銷。
          延遲加載特性的出現(xiàn),正是為了解決這個問題。
          所謂延遲加載,就是在需要數(shù)據(jù)的時候,才真正執(zhí)行數(shù)據(jù)加載操作。
          對于我們這里的user對象的加載過程,也就意味著,加載user對象時只針對其本身的屬性,
          而當(dāng)我們需要獲取user對象所關(guān)聯(lián)的address信息時(如執(zhí)行user.getAddresses時),才
          真正從數(shù)據(jù)庫中加載address數(shù)據(jù)并返回。
          我們將前面一對多關(guān)系中的lazy屬性修改為true,即指定了關(guān)聯(lián)對象采用延遲加載:
          <hibernate-mapping>
          <class
          name="org.hibernate.sample.TUser"
          table="t_user"
          dynamic-update="true"
          dynamic-insert="true"
          >
          ……
          <set
          name="addresses"
          table="t_address"
          lazy="true" ★
          inverse="false"
          cascade="all"
          sort="unsorted"
          order-by="zipcode asc"
          >
          <key
          column="user_id"
          >
          </key>
          <one-to-many
          class="org.hibernate.sample.TAddress"
          />
          </set>
          ……
          </class>
          </hibernate-mapping>
          嘗試執(zhí)行以下代碼:
          Criteria criteria = session.createCriteria(TUser.class);
          criteria.add(Expression.eq("name","Erica"));
          List userList = criteria.list();
          TUser user =(TUser)userList.get(0);
          System.out.println("User name => "+user.getName());
          Set hset = user.getAddresses();
          session.close();//關(guān)閉Session
          TAddress addr = (TAddress)hset.toArray()[0];
          System.out.println(addr.getAddress());
          運行時拋出異常:
          LazyInitializationException - Failed to lazily initialize a
          collection - no session or session was closed
          如果我們稍做調(diào)整,將session.close放在代碼末尾,則不會發(fā)生這樣的問題。
          這意味著,只有我們實際加載user關(guān)聯(lián)的address時,Hibernate才試圖通過
          session從數(shù)據(jù)庫中加載實際的數(shù)據(jù)集,而由于我們讀取address之前已經(jīng)關(guān)閉了
          session,所以報出session已關(guān)閉的錯誤。
          這里有個問題,如果我們采用了延遲加載機制,但希望在一些情況下,實現(xiàn)非延遲加
          載時的功能,也就是說,我們希望在Session關(guān)閉后,依然允許操作user的addresses
          屬性。如,為了向View層提供數(shù)據(jù),我們必須提供一個完整的User對象,包含其所關(guān)聯(lián)的
          address信息,而這個User對象必須在Session關(guān)閉之后仍然可以使用。
          Hibernate.initialize方法可以通過強制加載關(guān)聯(lián)對象實現(xiàn)這一功能:
          Hibernate.initialize(user.getAddresses());
          session.close();
          //通過Hibernate.initialize方法強制讀取數(shù)據(jù)
          //addresses對象即可脫離session進行操作
          Set hset= user.getAddresses();
          TAddress addr = (TAddress)hset.toArray()[0];
          System.out.println(addr.getAddress());
          為了實現(xiàn)透明化的延遲加載機制,hibernate進行了大量努力。其中包括JDK
          Collection接口的獨立實現(xiàn)。
          如果我們嘗試用HashSet強行轉(zhuǎn)化Hibernate返回的Set型對象:
          Set hset = (HashSet)user.getAddresses();
          就會在運行期得到一個java.lang.ClassCastException,實際上,此時返回的是
          一個Hibernate的特定Set實現(xiàn)“net.sf.hibernate.collection.Set”對象,而非
          傳統(tǒng)意義上的JDK Set實現(xiàn)。
          這也正是我們?yōu)槭裁丛诰帉慞OJO時,必須用JDK Collection接口(如Set,Map),
          而非特定的JDK Collection實現(xiàn)類(如HashSet、HashMap)申明Collection屬性的
          原因。
          回到前面TUser類的定義:
          public class TUser implements Serializable {
          ……
          private Set addresses = new HashSet();
          ……
          }
          我們通過Set接口,申明了一個addresses屬性,并創(chuàng)建了一個HashSet作為
          addresses的初始實例,以便我們創(chuàng)建TUser實例后,就可以為其添加關(guān)聯(lián)的address對
          象:
          TUser user = new TUser();
          TAddress addr = new TAddress();
          addr.setAddress("Hongkong");
          user.getAddresses().add(addr);
          session.save(user);
          此時,這里的addresses屬性還是一個HashSet對象,其中包含了一個address對象
          的引用。那么,當(dāng)調(diào)用session.save(user)時,Hibernate是如何處理這個HashSet
          型屬性的呢?
          通過Eclipse的Debug窗口,我們可以看到session.save方法執(zhí)行前后user對象發(fā)
          生的變化:
          圖一 session.save方法之前的user對象
          圖二 session.save方法之后的user對象
          可以看到,user對象在通過Hibernate處理之后已經(jīng)發(fā)生了變化。
          首先,由于insert操作,Hibernate獲得數(shù)據(jù)庫產(chǎn)生的id值(在我們的例子中,采
          用native方式的主鍵生成機制),并填充到user對象的id屬性。這個變化比較容易理解。
          另一方面,Hibernate使用了自己的Collection實現(xiàn)
          “net.sf.hibernate.collection.Set”對user中的HashSet型addresses屬性進
          行了替換,并用數(shù)據(jù)對其進行填充,保證新的addresses與原有的addresses包含同樣的
          實體元素。
          由于擁有自身的Collection實現(xiàn),Hibernate就可以在Collection層從容的實現(xiàn)
          延遲加載特性。只有程序真正讀取這個Collection時,才激發(fā)底層實際的數(shù)據(jù)庫操作。
          事務(wù)管理
          Hibernate 是JDBC 的輕量級封裝,本身并不具備事務(wù)管理能力。在事務(wù)管理層,
          Hibernate將其委托給底層的JDBC或者JTA,以實現(xiàn)事務(wù)管理和調(diào)度功能。
          Hibernate的默認事務(wù)處理機制基于JDBC Transaction。我們也可以通過配置文
          件設(shè)定采用JTA作為事務(wù)管理實現(xiàn):
          <hibernate-configuration>
          <session-factory>
          ……
          <property name="hibernate.transaction.factory_class">
          net.sf.hibernate.transaction.JTATransactionFactory
          <!--net.sf.hibernate.transaction.JDBCTransactionFactory-->
          </property>
          ……
          </session-factory>
          </hibernate-configuration>
          基于JDBC的事務(wù)管理:
          將事務(wù)管理委托給JDBC 進行處理無疑是最簡單的實現(xiàn)方式,Hibernate 對于JDBC
          事務(wù)的封裝也極為簡單。
          我們來看下面這段代碼:
          session = sessionFactory.openSession();
          Transaction tx = session.beginTransaction();
          ……
          tx.commit();
          從JDBC層面而言,上面的代碼實際上對應(yīng)著:
          Connection dbconn = getConnection();
          dbconn.setAutoCommit(false);
          ……
          dbconn.commit();
          就是這么簡單,Hibernate并沒有做更多的事情(實際上也沒法做更多的事情),只
          是將這樣的JDBC代碼進行了封裝而已。
          這里要注意的是,在sessionFactory.openSession()中,hibernate會初始化
          數(shù)據(jù)庫連接,與此同時,將其AutoCommit 設(shè)為關(guān)閉狀態(tài)(false)。而其后,在
          Session.beginTransaction 方法中,Hibernate 會再次確認Connection 的
          AutoCommit 屬性被設(shè)為關(guān)閉狀態(tài)( 為了防止用戶代碼對session 的
          Connection.AutoCommit屬性進行修改)。
          這也就是說,我們一開始從SessionFactory獲得的session,其自動提交屬性就
          已經(jīng)被關(guān)閉(AutoCommit=false),下面的代碼將不會對數(shù)據(jù)庫產(chǎn)生任何效果:
          session = sessionFactory.openSession();
          session.save(user);
          session.close();
          這實際上相當(dāng)于 JDBC Connection的AutoCommit屬性被設(shè)為false,執(zhí)行了若
          干JDBC操作之后,沒有調(diào)用commit操作即將Connection關(guān)閉。
          如果要使代碼真正作用到數(shù)據(jù)庫,我們必須顯式的調(diào)用Transaction指令:
          session = sessionFactory.openSession();
          Transaction tx = session.beginTransaction();
          session.save(user);
          tx.commit();
          session.close();
          基于JTA的事務(wù)管理:
          JTA 提供了跨Session 的事務(wù)管理能力。這一點是與JDBC Transaction 最大的
          差異。
          JDBC事務(wù)由Connnection管理,也就是說,事務(wù)管理實際上是在JDBC Connection
          中實現(xiàn)。事務(wù)周期限于Connection的生命周期之類。同樣,對于基于JDBC Transaction
          的Hibernate 事務(wù)管理機制而言,事務(wù)管理在Session 所依托的JDBC Connection
          中實現(xiàn),事務(wù)周期限于Session的生命周期。
          JTA 事務(wù)管理則由 JTA 容器實現(xiàn),JTA 容器對當(dāng)前加入事務(wù)的眾多Connection 進
          行調(diào)度,實現(xiàn)其事務(wù)性要求。JTA的事務(wù)周期可橫跨多個JDBC Connection生命周期。
          同樣對于基于JTA事務(wù)的Hibernate而言,JTA事務(wù)橫跨可橫跨多個Session。
          下面這幅圖形象的說明了這個問題:
          圖中描述的是JDBC Connection 與事務(wù)之間的關(guān)系,而Hibernate Session 在
          這里與JDBC Connection具備同等的邏輯含義。
          從上圖中我們可以看出,JTA 事務(wù)是由JTA Container 維護,而參與事務(wù)的
          Connection無需對事務(wù)管理進行干涉。這也就是說,如果采用JTA Transaction,我
          們不應(yīng)該再調(diào)用Hibernate的Transaction功能。
          上面基于JDBC Transaction的正確代碼,這里就會產(chǎn)生問題:
          public class ClassA{
          public void saveUser(User user){
          session = sessionFactory.openSession();
          Transaction tx = session.beginTransaction();
          session.save(user);
          tx.commit();
          session.close();
          }
          }
          public class ClassB{
          public void saveOrder(Order order){
          session = sessionFactory.openSession();
          Transaction tx = session.beginTransaction();
          session.save(order);
          tx.commit();
          session.close();
          }
          }
          public class ClassC{
          public void save(){
          ……
          UserTransaction tx = new InitialContext().lookup(“……”);
          ClassA.save(user);
          ClassB.save(order);
          tx.commit();
          ……
          }
          }
          這里有兩個類ClassA和ClassB,分別提供了兩個方法:saveUser和saveOrder,
          用于保存用戶信息和訂單信息。在ClassC中,我們接連調(diào)用了ClassA.saveUser方法
          和ClassB.saveOrder 方法,同時引入了JTA 中的UserTransaction 以實現(xiàn)
          ClassC.save方法中的事務(wù)性。
          問題出現(xiàn)了,ClassA 和ClassB 中分別都調(diào)用了Hibernate 的Transaction 功
          能。在Hibernate 的JTA 封裝中,Session.beginTransaction 同樣也執(zhí)行了
          InitialContext.lookup方法獲取UserTransaction實例,Transaction.commit
          方法同樣也調(diào)用了UserTransaction.commit方法。實際上,這就形成了兩個嵌套式的
          JTA Transaction:ClassC 申明了一個事務(wù),而在ClassC 事務(wù)周期內(nèi),ClassA 和
          ClassB也企圖申明自己的事務(wù),這將導(dǎo)致運行期錯誤。
          因此,如果決定采用JTA Transaction,應(yīng)避免再重復(fù)調(diào)用Hibernate 的
          Transaction功能,上面的代碼修改如下:
          public class ClassA{
          public void save(TUser user){
          session = sessionFactory.openSession();
          session.save(user);
          session.close();
          }
          ……
          }
          public class ClassB{
          public void save (Order order){
          session = sessionFactory.openSession();
          session.save(order);
          session.close();
          }
          ……
          }
          public class ClassC{
          public void save(){
          ……
          UserTransaction tx = new InitialContext().lookup(“……”);
          classA.save(user);
          classB.save(order);
          tx.commit();
          ……
          }
          }
          上面代碼中的ClassC.save方法,也可以改成這樣:
          public class ClassC{
          public void save(){
          ……
          session = sessionFactory.openSession();
          Transaction tx = session.beginTransaction();
          classA.save(user);
          classB.save(order);
          tx.commit();
          ……
          }
          }
          實際上,這是利用Hibernate來完成啟動和提交UserTransaction的功能,但這
          樣的做法比原本直接通過InitialContext獲取UserTransaction 的做法消耗了更多
          的資源,得不償失。
          在EJB 中使用JTA Transaction 無疑最為簡便,我們只需要將save 方法配置為
          JTA事務(wù)支持即可,無需顯式申明任何事務(wù),下面是一個Session Bean的save方法,
          它的事務(wù)屬性被申明為“Required”,EJB容器將自動維護此方法執(zhí)行過程中的事務(wù):
          /**
          * @ejb.interface-method
          * view-type="remote"
          *
          * @ejb.transaction type = "Required"
          **/
          public void save(){
          //EJB環(huán)境中,通過部署配置即可實現(xiàn)事務(wù)申明,而無需顯式調(diào)用事務(wù)
          classA.save(user);
          classB.save(log);
          }//方法結(jié)束時,如果沒有異常發(fā)生,則事務(wù)由EJB容器自動提交。
          鎖(locking)
          業(yè)務(wù)邏輯的實現(xiàn)過程中,往往需要保證數(shù)據(jù)訪問的排他性。如在金融系統(tǒng)的日終結(jié)算
          處理中,我們希望針對某個cut-off時間點的數(shù)據(jù)進行處理,而不希望在結(jié)算進行過程中
          (可能是幾秒種,也可能是幾個小時),數(shù)據(jù)再發(fā)生變化。此時,我們就需要通過一些機
          制來保證這些數(shù)據(jù)在某個操作過程中不會被外界修改,這樣的機制,在這里,也就是所謂
          的“鎖”,即給我們選定的目標(biāo)數(shù)據(jù)上鎖,使其無法被其他程序修改。
          Hibernate支持兩種鎖機制:即通常所說的“悲觀鎖(Pessimistic Locking)”
          和“樂觀鎖(Optimistic Locking)”。
          悲觀鎖(Pessimistic Locking)
          悲觀鎖,正如其名,它指的是對數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來自
          外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度,因此,在整個數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定
          狀態(tài)。悲觀鎖的實現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機制(也只有數(shù)據(jù)庫層提供的鎖機制才能
          真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實現(xiàn)了加鎖機制,也無法保證外部系
          統(tǒng)不會修改數(shù)據(jù))。
          一個典型的倚賴數(shù)據(jù)庫的悲觀鎖調(diào)用:
          select * from account where name=”Erica” for update
          這條sql 語句鎖定了account 表中所有符合檢索條件(name=”Erica”)的記錄。
          本次事務(wù)提交之前(事務(wù)提交時會釋放事務(wù)過程中的鎖),外界無法修改這些記錄。
          Hibernate的悲觀鎖,也是基于數(shù)據(jù)庫的鎖機制實現(xiàn)。
          下面的代碼實現(xiàn)了對查詢記錄的加鎖:
          String hqlStr =
          "from TUser as user where user.name='Erica'";
          Query query = session.createQuery(hqlStr);
          query.setLockMode("user",LockMode.UPGRADE); //加鎖
          List userList = query.list();//執(zhí)行查詢,獲取數(shù)據(jù)
          query.setLockMode對查詢語句中,特定別名所對應(yīng)的記錄進行加鎖(我們?yōu)?br />TUser類指定了一個別名“user”),這里也就是對返回的所有user記錄進行加鎖。
          觀察運行期Hibernate生成的SQL語句:
          select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id
          as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex
          from t_user tuser0_ where (tuser0_.name='Erica' ) for update
          這里Hibernate通過使用數(shù)據(jù)庫的for update子句實現(xiàn)了悲觀鎖機制。
          Hibernate的加鎖模式有:
          ? LockMode.NONE : 無鎖機制。
          ? LockMode.WRITE :Hibernate在Insert和Update記錄的時候會自動
          獲取。
          ? LockMode.READ : Hibernate在讀取記錄的時候會自動獲取。
          以上這三種鎖機制一般由Hibernate內(nèi)部使用,如Hibernate為了保證Update
          過程中對象不會被外界修改,會在save方法實現(xiàn)中自動為目標(biāo)對象加上WRITE鎖。
          ? LockMode.UPGRADE :利用數(shù)據(jù)庫的for update子句加鎖。
          ? LockMode. UPGRADE_NOWAIT :Oracle的特定實現(xiàn),利用Oracle的for
          update nowait子句實現(xiàn)加鎖。
          上面這兩種鎖機制是我們在應(yīng)用層較為常用的,加鎖一般通過以下方法實現(xiàn):
          Criteria.setLockMode
          Query.setLockMode
          Session.lock
          注意,只有在查詢開始之前(也就是Hiberate 生成SQL 之前)設(shè)定加鎖,才會
          真正通過數(shù)據(jù)庫的鎖機制進行加鎖處理,否則,數(shù)據(jù)已經(jīng)通過不包含for update
          子句的Select SQL加載進來,所謂數(shù)據(jù)庫加鎖也就無從談起。
          樂觀鎖(Optimistic Locking)
          相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制。悲觀鎖大多數(shù)情況下依
          靠數(shù)據(jù)庫的鎖機制實現(xiàn),以保證操作最大程度的獨占性。但隨之而來的就是數(shù)據(jù)庫
          性能的大量開銷,特別是對長事務(wù)而言,這樣的開銷往往無法承受。
          如一個金融系統(tǒng),當(dāng)某個操作員讀取用戶的數(shù)據(jù),并在讀出的用戶數(shù)據(jù)的基礎(chǔ)上進
          行修改時(如更改用戶帳戶余額),如果采用悲觀鎖機制,也就意味著整個操作過
          程中(從操作員讀出數(shù)據(jù)、開始修改直至提交修改結(jié)果的全過程,甚至還包括操作
          員中途去煮咖啡的時間),數(shù)據(jù)庫記錄始終處于加鎖狀態(tài),可以想見,如果面對幾
          百上千個并發(fā),這樣的情況將導(dǎo)致怎樣的后果。
          樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基于數(shù)據(jù)版本
          (Version)記錄機制實現(xiàn)。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個版本標(biāo)識,在基于
          數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個“version”字段來
          實現(xiàn)。
          讀取出數(shù)據(jù)時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提
          交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對應(yīng)記錄的當(dāng)前版本信息進行比對,如果提交的數(shù)據(jù)
          版本號大于數(shù)據(jù)庫表當(dāng)前版本號,則予以更新,否則認為是過期數(shù)據(jù)。
          對于上面修改用戶帳戶信息的例子而言,假設(shè)數(shù)據(jù)庫中帳戶信息表中有一個
          version字段,當(dāng)前值為1;而當(dāng)前帳戶余額字段(balance)為$100。
          1 操作員A 此時將其讀出(version=1),并從其帳戶余額中扣除$50
          ($100-$50)。
          2 在操作員A操作的過程中,操作員B也讀入此用戶信息(version=1),并
          從其帳戶余額中扣除$20($100-$20)。
          3 操作員A完成了修改工作,將數(shù)據(jù)版本號加一(version=2),連同帳戶扣
          除后余額(balance=$50),提交至數(shù)據(jù)庫更新,此時由于提交數(shù)據(jù)版本大
          于數(shù)據(jù)庫記錄當(dāng)前版本,數(shù)據(jù)被更新,數(shù)據(jù)庫記錄version更新為2。
          4 操作員B完成了操作,也將版本號加一(version=2)試圖向數(shù)據(jù)庫提交數(shù)
          據(jù)(balance=$80),但此時比對數(shù)據(jù)庫記錄版本時發(fā)現(xiàn),操作員B提交的
          數(shù)據(jù)版本號為2,數(shù)據(jù)庫記錄當(dāng)前版本也為2,不滿足“提交版本必須大于記
          錄當(dāng)前版本才能執(zhí)行更新“的樂觀鎖策略,因此,操作員B 的提交被駁回。
          這樣,就避免了操作員B 用基于version=1 的舊數(shù)據(jù)修改的結(jié)果覆蓋操作
          員A的操作結(jié)果的可能。
          從上面的例子可以看出,樂觀鎖機制避免了長事務(wù)中的數(shù)據(jù)庫加鎖開銷(操作員A
          和操作員B操作過程中,都沒有對數(shù)據(jù)庫數(shù)據(jù)加鎖),大大提升了大并發(fā)量下的系
          統(tǒng)整體性能表現(xiàn)。
          需要注意的是,樂觀鎖機制往往基于系統(tǒng)中的數(shù)據(jù)存儲邏輯,因此也具備一定的局
          限性,如在上例中,由于樂觀鎖機制是在我們的系統(tǒng)中實現(xiàn),來自外部系統(tǒng)的用戶
          余額更新操作不受我們系統(tǒng)的控制,因此可能會造成臟數(shù)據(jù)被更新到數(shù)據(jù)庫中。在
          系統(tǒng)設(shè)計階段,我們應(yīng)該充分考慮到這些情況出現(xiàn)的可能性,并進行相應(yīng)調(diào)整(如
          將樂觀鎖策略在數(shù)據(jù)庫存儲過程中實現(xiàn),對外只開放基于此存儲過程的數(shù)據(jù)更新途
          徑,而不是將數(shù)據(jù)庫表直接對外公開)。
          Hibernate 在其數(shù)據(jù)訪問引擎中內(nèi)置了樂觀鎖實現(xiàn)。如果不用考慮外部系統(tǒng)對數(shù)
          據(jù)庫的更新操作,利用Hibernate提供的透明化樂觀鎖實現(xiàn),將大大提升我們的
          生產(chǎn)力。
          Hibernate中可以通過class描述符的optimistic-lock屬性結(jié)合version
          描述符指定。
          現(xiàn)在,我們?yōu)橹笆纠械腡User加上樂觀鎖機制。
          1. 首先為TUser的class描述符添加optimistic-lock屬性:
          <hibernate-mapping>
          <class
          name="org.hibernate.sample.TUser"
          table="t_user"
          dynamic-update="true"
          dynamic-insert="true"
          optimistic-lock="version"
          >
          ……
          </class>
          </hibernate-mapping>
          optimistic-lock屬性有如下可選取值:
          ? none
          無樂觀鎖
          ? version
          通過版本機制實現(xiàn)樂觀鎖
          ? dirty
          通過檢查發(fā)生變動過的屬性實現(xiàn)樂觀鎖
          ? all
          通過檢查所有屬性實現(xiàn)樂觀鎖
          其中通過version實現(xiàn)的樂觀鎖機制是Hibernate官方推薦的樂觀鎖實現(xiàn),同時也
          是Hibernate中,目前唯一在數(shù)據(jù)對象脫離Session發(fā)生修改的情況下依然有效的鎖機
          制。因此,一般情況下,我們都選擇version方式作為Hibernate樂觀鎖實現(xiàn)機制。
          2. 添加一個Version屬性描述符
          <hibernate-mapping>
          <class
          name="org.hibernate.sample.TUser"
          table="t_user"
          dynamic-update="true"
          dynamic-insert="true"
          optimistic-lock="version"
          >
          <id
          name="id"
          column="id"
          type="java.lang.Integer"
          >
          <generator class="native">
          </generator>
          </id>
          <version
          column="version"
          name="version"
          type="java.lang.Integer"
          />
          ……
          </class>
          </hibernate-mapping>
          注意version 節(jié)點必須出現(xiàn)在ID 節(jié)點之后。
          這里我們聲明了一個version屬性,用于存放用戶的版本信息,保存在TUser表的
          version字段中。
          此時如果我們嘗試編寫一段代碼,更新TUser表中記錄數(shù)據(jù),如:
          Criteria criteria = session.createCriteria(TUser.class);
          criteria.add(Expression.eq("name","Erica"));
          List userList = criteria.list();
          TUser user =(TUser)userList.get(0);
          Transaction tx = session.beginTransaction();
          user.setUserType(1); //更新UserType字段
          tx.commit();
          每次對TUser進行更新的時候,我們可以發(fā)現(xiàn),數(shù)據(jù)庫中的version都在遞增。
          而如果我們嘗試在tx.commit 之前,啟動另外一個Session,對名為Erica 的用
          戶進行操作,以模擬并發(fā)更新時的情形:
          Session session= getSession();
          Criteria criteria = session.createCriteria(TUser.class);
          criteria.add(Expression.eq("name","Erica"));
          Session session2 = getSession();
          Criteria criteria2 = session2.createCriteria(TUser.class);
          criteria2.add(Expression.eq("name","Erica"));
          List userList = criteria.list();
          List userList2 = criteria2.list();
          TUser user =(TUser)userList.get(0);
          TUser user2 =(TUser)userList2.get(0);
          Transaction tx = session.beginTransaction();
          Transaction tx2 = session2.beginTransaction();
          user2.setUserType(99);
          tx2.commit();
          user.setUserType(1);
          tx.commit();
          執(zhí)行以上代碼,代碼將在tx.commit()處拋出StaleObjectStateException異
          常,并指出版本檢查失敗,當(dāng)前事務(wù)正在試圖提交一個過期數(shù)據(jù)。通過捕捉這個異常,我
          們就可以在樂觀鎖校驗失敗時進行相應(yīng)處理。
          Hibernate分頁
          數(shù)據(jù)分頁顯示,在系統(tǒng)實現(xiàn)中往往帶來了較大的工作量,對于基于JDBC的程序而言,
          不同數(shù)據(jù)庫提供的分頁(部分讀?。┠J酵鞑幌嗤矌砹藬?shù)據(jù)庫間可移植性上的
          問題。
          Hibernate中,通過對不同數(shù)據(jù)庫的統(tǒng)一接口設(shè)計,實現(xiàn)了透明化、通用化的分頁實
          現(xiàn)機制。
          我們可以通過Criteria.setFirstResult和Criteria.setFetchSize方法設(shè)
          定分頁范圍,如:
          Criteria criteria = session.createCriteria(TUser.class);
          criteria.add(Expression.eq("age","20"));
          //從檢索結(jié)果中獲取第100條記錄開始的20條記錄
          criteria.setFirstResult(100);
          criteria.setFetchSize(20);
          同樣,Query接口也提供了與其一致的方法。
          Hibernate中,抽象類net.sf.hibernate.dialect指定了所有底層數(shù)據(jù)庫的對
          外統(tǒng)一接口。通過針對不同數(shù)據(jù)庫提供相應(yīng)的dialect實現(xiàn),數(shù)據(jù)庫之間的差異性得以消
          除,從而為上層機制提供了透明的、數(shù)據(jù)庫無關(guān)的存儲層基礎(chǔ)。
          對于分頁機制而言, dialect中定義了一個方法如下:
          Public String getLimitString(
          String querySelect,
          boolean hasOffset
          )
          此方法用于在現(xiàn)有Select 語句基礎(chǔ)上,根據(jù)各數(shù)據(jù)庫自身特性,構(gòu)造對應(yīng)的記錄返
          回限定子句。如MySQL中對應(yīng)的記錄限定子句為Limit,而Oracle中,可通過rownum
          子句實現(xiàn)。
          我們來看MySQLDialect中的getLimitString實現(xiàn):
          public String getLimitString(String sql, boolean hasOffset) {
          return new StringBuffer( sql.length()+20 )
          .append(sql)
          .append( hasOffset ? " limit ?, ?" : " limit ?")
          .toString();
          }
          從上面可以看到,MySQLDialect.getLimitString方法的實現(xiàn)實際上是在給定的
          Select語句后追加MySQL所提供的專有SQL子句limit來實現(xiàn)。
          下面是Oracle9Dialect 中的getLimitString 實現(xiàn),其中通過Oracle 特有的
          rownum子句實現(xiàn)了數(shù)據(jù)的部分讀取。
          public String getLimitString(String sql, boolean hasOffset)
          {
          StringBuffer pagingSelect =
          new StringBuffer( sql.length()+100 );
          if (hasOffset) {
          pagingSelect.append(
          "select * from ( select row_.*, rownum rownum_ from ( "
          );
          }else {
          pagingSelect.append("select * from ( ");
          }
          pagingSelect.append(sql);
          if (hasOffset) {
          pagingSelect.append(
          " ) row_ where rownum <= ?) where rownum_ > ?"
          );
          }else {
          pagingSelect.append(" ) where rownum <= ?");
          }
          return pagingSelect.toString();
          }
          大多數(shù)主流數(shù)據(jù)庫都提供了數(shù)據(jù)部分讀取機制,而對于某些沒有提供相應(yīng)機制的數(shù)據(jù)
          庫而言,Hibernate也通過其他途徑實現(xiàn)了分頁,如通過Scrollable ResultSet,如
          果JDBC 不支持Scrollable ResultSet,Hibernate 也會自動通過ResultSet 的
          next 方法進行記錄定位。這樣,Hibernate 通過底層對分頁機制的良好封裝,使得開發(fā)
          人員無需關(guān)心數(shù)據(jù)分頁的細節(jié)實現(xiàn),將數(shù)據(jù)邏輯和存儲邏輯分離開來,在提高生產(chǎn)效率的
          同時,也大大加強了系統(tǒng)在不同數(shù)據(jù)庫平臺之間的可移植性。
          Cache管理
          Cache往往是提高系統(tǒng)性能的最重要的手段。在筆者記憶中,DOS時代SmartDrv2所
          帶來的磁盤讀寫性能提升還歷歷在目(記得95年時安裝Windowns 3.0,在沒有SmartDrv
          常駐內(nèi)存的情況下,大概需要15分鐘左右,而加載了SmartDrv,只需要2分鐘即可完成
          整個安裝過程)。
          Cache對于大量倚賴數(shù)據(jù)讀取操作的系統(tǒng)而言(典型的,如114查號系統(tǒng))尤為重要,
          在大并發(fā)量的情況下,如果每次程序都需要向數(shù)據(jù)庫直接做查詢操作,所帶來的性能開銷
          顯而易見,頻繁的網(wǎng)絡(luò)傳輸、數(shù)據(jù)庫磁盤的讀寫操作(大多數(shù)數(shù)據(jù)庫本身也有Cache,但
          即使如此,訪問數(shù)據(jù)庫本身的開銷也極為可觀),這些都大大降低了系統(tǒng)的整體性能。
          此時,如果能把數(shù)據(jù)在本地內(nèi)存中保留一個鏡像,下次訪問時只需從內(nèi)存中直接獲取,
          那么顯然可以帶來顯著的性能提升(可能是幾倍,甚至幾十倍的整體讀取性能提升).
          引入Cache機制的難點是如何保證內(nèi)存中數(shù)據(jù)的有效性,否則臟數(shù)據(jù)的出現(xiàn)將給系統(tǒng)
          帶來難以預(yù)知的嚴重后果。
          Hibernate 中實現(xiàn)了良好的Cache 機制,我們可以借助Hibernate 內(nèi)部的Cache
          迅速提高系統(tǒng)數(shù)據(jù)讀取性能。
          需要注意的是:Hibernate做為一個應(yīng)用級的數(shù)據(jù)訪問層封裝,只能在其作用范圍內(nèi)
          保持Cache中數(shù)據(jù)的的有效性,也就是說,在我們的系統(tǒng)與第三方系統(tǒng)共享數(shù)據(jù)庫的情況
          下,Hibernate的Cache機制可能失效。
          Hibernate 在本地JVM 中維護了一個緩沖池,并將從數(shù)據(jù)庫獲得的數(shù)據(jù)保存到池中
          以供下次重復(fù)使用(如果在Hibernate中數(shù)據(jù)發(fā)生了變動,Hibernate同樣也會更新池
          中的數(shù)據(jù)版本)。
          此時,如果有第三方系統(tǒng)對數(shù)據(jù)庫進行了更改,那么,Hibernate并不知道數(shù)據(jù)庫中
          的數(shù)據(jù)已經(jīng)發(fā)生了變化,也就是說,池中的數(shù)據(jù)還是修改之前的版本,下次讀取時,
          Hibernate會將此數(shù)據(jù)返回給上層代碼,從而導(dǎo)致潛在的問題。
          外部系統(tǒng)的定義,并非限于本系統(tǒng)之外的第三方系統(tǒng),即使在本系統(tǒng)中,如果出現(xiàn)了
          繞過Hibernate數(shù)據(jù)存儲機制的其他數(shù)據(jù)存取手段,那么Cache的有效性也必須細加考
          量。如,在同一套系統(tǒng)中,基于Hibernate和基于JDBC的兩種數(shù)據(jù)訪問方式并存,那么
          通過JDBC更新數(shù)據(jù)庫的時候,Hibernate同樣無法獲知數(shù)據(jù)更新的情況,從而導(dǎo)致臟數(shù)
          據(jù)的出現(xiàn)。
          基于Java 的Cache 實現(xiàn),最簡單的莫過于HashTable,hibernate 提供了基于
          Hashtable 的Cache 實現(xiàn)機制,不過,由于其性能和功能上的局限,僅供開發(fā)調(diào)試中使
          用。同時,Hibernate 還提供了面向第三方Cache 實現(xiàn)的接口,如JCS、EHCache、
          OSCache、JBoss Cache、SwarmCache等。
          Hibernate中的Cache大致分為兩層,第一層Cache在Session實現(xiàn),屬于事務(wù)
          級數(shù)據(jù)緩沖,一旦事務(wù)結(jié)束,這個Cache 也就失效。此層Cache 為內(nèi)置實現(xiàn),無需我們
          2 DOS下的磁盤讀寫緩沖程序
          進行干涉。
          第二層Cache,是Hibernate 中對其實例范圍內(nèi)的數(shù)據(jù)進行緩存的管理容器。也是
          這里我們討論的主題。
          Hibernate早期版本中采用了JCS(Java Caching System -Apache Turbine
          項目中的一個子項目)作為默認的第二層Cache實現(xiàn)。由于JCS的發(fā)展停頓,以及其內(nèi)在
          的一些問題(在某些情況下,可能導(dǎo)致內(nèi)存泄漏以及死鎖),新版本的Hibernate已經(jīng)將
          JCS去除,并用EHCache作為其默認的第二級Cache實現(xiàn)。
          相對JCS,EHCache更加穩(wěn)定,并具備更好的緩存調(diào)度性能,缺陷是目前還無法做到
          分布式緩存,如果我們的系統(tǒng)需要在多臺設(shè)備上部署,并共享同一個數(shù)據(jù)庫,必須使用支
          持分布式緩存的Cache實現(xiàn)(如JCS、JBossCache)以避免出現(xiàn)不同系統(tǒng)實例之間緩存
          不一致而導(dǎo)致臟數(shù)據(jù)的情況。
          Hibernate對Cache進行了良好封裝,透明化的Cache機制使得我們在上層結(jié)構(gòu)的
          實現(xiàn)中無需面對繁瑣的Cache維護細節(jié)。
          目前Hibernate支持的Cache實現(xiàn)有:
          名稱 類 集群支持 查詢緩沖
          HashTable net.sf.hibernate.cache.HashtableCacheP
          rovider
          N Y
          EHCache net.sf.ehcache.hibernate.Provider N Y
          OSCache net.sf.hibernate.cache.OSCacheProvider N Y
          SwarmCache net.sf.hibernate.cache.SwarmCachePro
          vider
          Y
          JBossCache net.sf.hibernate.cache.TreeCacheProvid
          er
          Y
          其中SwarmCache和JBossCache均提供了分布式緩存實現(xiàn)(Cache集群)。
          其中SwarmCache 提供的是invalidation 方式的分布式緩存,即當(dāng)集群中的某個
          節(jié)點更新了緩存中的數(shù)據(jù),即通知集群中的其他節(jié)點將此數(shù)據(jù)廢除,之后各個節(jié)點需要用
          到這個數(shù)據(jù)的時候,會重新從數(shù)據(jù)庫中讀入并填充到緩存中。
          而JBossCache提供的是Reapplication式的緩沖,即如果集群中某個節(jié)點的數(shù)據(jù)
          發(fā)生改變,此節(jié)點會將發(fā)生改變的數(shù)據(jù)的最新版本復(fù)制到集群中的每個節(jié)點中以保持所有
          節(jié)點狀態(tài)一致。
          使用第二層Cache,需要在hibernate.cfg.xml配置以下參數(shù)(以EHCache為例):
          <hibernate-configuration>
          <session-factory>
          ……
          <property name="hibernate.cache.provider_class">
          net.sf.ehcache.hibernate.Provider
          </property>
          ……
          </session-factory>
          </hibernate-configuration>
          另外還需要針對Cache實現(xiàn)本身進行配置,如EHCache的配置文件:
          <ehcache>
          <diskStore path="java.io.tmpdir"/>
          <defaultCache
          maxElementsInMemory="10000" //Cache中最大允許保存的數(shù)據(jù)數(shù)量
          eternal="false" //Cache中數(shù)據(jù)是否為常量
          timeToIdleSeconds="120" //緩存數(shù)據(jù)鈍化時間
          timeToLiveSeconds="120" //緩存數(shù)據(jù)的生存時間
          overflowToDisk="true" //內(nèi)存不足時,是否啟用磁盤緩存
          />
          </ehcache>
          其中“//”開始的注釋是筆者追加,實際配置文件中不應(yīng)出現(xiàn)。
          之后,需要在我們的映射文件中指定各個映射實體的Cache策略:
          <class name=" org.hibernate.sample.TUser" .... >
          <cache usage="read-write"/>
          ....
          <set name="addresses" .... >
          <cache usage="read-only"/>
          ....
          </set>
          </class>
          緩沖描述符cache可用于描述映射類和集合屬性。
          上例中,Class 節(jié)點下的cache 描述符指定了針對類TUser 的緩存策略為
          “read-write”,即緩沖中的TUser實例為可讀可寫,而集合屬性addresses 的緩存
          策略為只讀。
          cache usage可選值有以下幾種:
          1. read-only
          只讀。
          2. read-write
          可讀可寫。
          3. nonstrict-read-write
          如果程序?qū)Σl(fā)數(shù)據(jù)修改要求不是非常嚴格,只是偶爾需要更新數(shù)據(jù),可以采用
          本選項,以減少無謂的檢查,獲得較好的性能。
          4. transactional
          事務(wù)性cache。在事務(wù)性Cache 中,Cache 的相關(guān)操作也被添加到事務(wù)之中,
          如果由于某種原因?qū)е率聞?wù)失敗,我們可以連同緩沖池中的數(shù)據(jù)一同回滾到事務(wù)
          開始之前的狀態(tài)。目前Hibernate 內(nèi)置的Cache 中,只有JBossCache 支持
          事務(wù)性的Cache實現(xiàn)。
          不同的Cache實現(xiàn),支持的usage也各不相同:
          名稱 read-only read-write nonstrict-read-write transactional
          HashTable Y Y Y
          EHCache Y Y Y
          OSCache Y Y Y
          SwarmCache Y Y
          JBossCache Y Y
          配置好Cache之后,Hibernate在運行期會自動應(yīng)用Cache機制,也就是說,我們
          對PO的更新,會自動同步到Cache中去,而數(shù)據(jù)的讀取,也會自動化的優(yōu)先從Cache中
          獲取,對于上層邏輯代碼而言,有沒有應(yīng)用Cache機制,并沒有什么影響。
          需要注意的是Hibernate 的數(shù)據(jù)庫查詢機制。我們從查詢結(jié)果中取出數(shù)據(jù)的時候,
          用的最多的是兩個方法:
          Query.list();
          Query.iterate();
          對于list方法而言,實際上Hibernate是通過一條Select SQL獲取所有的記錄。
          并將其讀出,填入到POJO中返回。
          而iterate 方法,則是首先通過一條Select SQL 獲取所有符合查詢條件的記錄的
          id,再對這個id 集合進行循環(huán)操作,通過單獨的Select SQL 取出每個id 所對應(yīng)的記
          錄,之后填入POJO中返回。
          也就是說,對于list 操作,需要一條SQL 完成。而對于iterate 操作,需要n+1
          條SQL。
          看上去iterate方法似乎有些多余,但在不同的情況下確依然有其獨特的功效,如對
          海量數(shù)據(jù)的查詢,如果用list方法將結(jié)果集一次取出,內(nèi)存的開銷可能無法承受。
          另一方面,對于我們現(xiàn)在的Cache機制而言,list方法將不會從Cache中讀取數(shù)據(jù),
          它總是一次性從數(shù)據(jù)庫中直接讀出所有符合條件的記錄。而iterate 方法因為每次根據(jù)
          id獲取數(shù)據(jù),這樣的實現(xiàn)機制也就為從Cache讀取數(shù)據(jù)提供了可能,hibernate首先會
          根據(jù)這個id 在本地Cache 內(nèi)尋找對應(yīng)的數(shù)據(jù),如果沒找到,再去數(shù)據(jù)庫中檢索。如果系
          統(tǒng)設(shè)計中對Cache比較倚重,則請注意編碼中這兩種不同方法的應(yīng)用組合,有針對性的改
          善代碼,最大程度提升系統(tǒng)的整體性能表現(xiàn)。
          通觀以上內(nèi)容,Hibernate通過對Cache的封裝,對上層邏輯層而言,實現(xiàn)了Cache
          的透明化實現(xiàn),程序員編碼時無需關(guān)心數(shù)據(jù)在Cache中的狀態(tài)和調(diào)度,從而最大化協(xié)調(diào)了
          性能和開發(fā)效率之間的平衡。
          Session管理
          無疑,Session是Hibernate運作的靈魂,作為貫穿Hibernate應(yīng)用的關(guān)鍵,Session
          中包含了數(shù)據(jù)庫操作相關(guān)的狀態(tài)信息。如對JDBC Connection 的維護,數(shù)據(jù)實體的狀態(tài)維持
          等。
          對Session 進行有效管理的意義,類似JDBC 程序設(shè)計中對于JDBC Connection 的調(diào)
          度管理。有效的Session管理機制,是Hibernate應(yīng)用設(shè)計的關(guān)鍵。
          大多數(shù)情況下,Session 管理的目標(biāo)聚焦于通過合理的設(shè)計,避免Session 的頻繁創(chuàng)建
          和銷毀,從而避免大量的內(nèi)存開銷和頻繁的JVM垃圾回收,保證系統(tǒng)高效平滑運行。
          在各種Session 管理方案中, ThreadLocal 模式得到了大量使用。ThreadLocal 是
          Java中一種較為特殊的線程綁定機制。通過ThreadLocal存取的數(shù)據(jù),總是與當(dāng)前線程相關(guān),
          也就是說,JVM 為每個運行的線程,綁定了私有的本地實例存取空間,從而為多線程環(huán)境常出
          現(xiàn)的并發(fā)訪問問題提供了一種隔離機制。
          首先,我們需要知道,SessionFactory負責(zé)創(chuàng)建Session,SessionFactory是線程
          安全的,多個并發(fā)線程可以同時訪問一個SessionFactory 并從中獲取Session 實例。而
          Session并非線程安全,也就是說,如果多個線程同時使用一個Session實例進行數(shù)據(jù)存取,
          則將會導(dǎo)致Session 數(shù)據(jù)存取邏輯混亂。下面是一個典型的Servlet,我們試圖通過一個類
          變量session實現(xiàn)Session的重用,以避免每次操作都要重新創(chuàng)建:
          public class TestServlet extends HttpServlet {
          private Session session;
          public void doGet( HttpServletRequest request,
          HttpServletResponse response)
          throws ServletException, IOException {
          session = getSession();
          doSomething();
          session.flush();
          }
          public void doSomething(){
          ......//基于session的存取操作
          }
          }
          代碼看上去正確無誤,甚至在我們單機測試的時候可能也不會發(fā)生什么問題,但這樣的代
          碼一旦編譯部署到實際運行環(huán)境中,接踵而來的莫名其妙的錯誤很可能會使得我們摸不找頭腦。
          問題出在哪里?
          首先,Servlet 運行是多線程的,而應(yīng)用服務(wù)器并不會為每個線程都創(chuàng)建一個Servlet
          實例,也就是說,TestServlet在應(yīng)用服務(wù)器中只有一個實例(在Tomcat中是這樣,其他的
          應(yīng)用服務(wù)器可能有不同的實現(xiàn)),而這個實例會被許多個線程并發(fā)調(diào)用,doGet 方法也將被不
          同的線程反復(fù)調(diào)用,可想而知,每次調(diào)用doGet 方法,這個唯一的TestServlet 實例的
          session 變量都會被重置,線程A 的運行過程中,其他的線程如果也被執(zhí)行,那么session
          的引用將發(fā)生改變,之后線程A 再調(diào)用session,可能此時的session 與其之前所用的
          session就不再一致,顯然,錯誤也就不期而至。
          ThreadLocal的出現(xiàn),使得這個問題迎刃而解。
          我們對上面的例子進行一些小小的修改:
          public class TestServlet extends HttpServlet {
          private ThreadLocal localSession = new ThreadLocal();
          public void doGet( HttpServletRequest request,
          HttpServletResponse response)
          throws ServletException, IOException {
          localSession.set(getSession());
          doSomething();
          session.flush();
          }
          public void doSomething(){
          Session session = (Session)localSession.get();
          ......//基于session的存取操作
          }
          }
          可以看到,localSession 是一個ThreadLocal 類型的對象,在doGet 方法中,我們
          通過其set 方法將獲取的session 實例保存,而在doSomething 方法中,通過get 方法取
          出session實例。
          這也就是ThreadLocal的獨特之處,它會為每個線程維護一個私有的變量空間。實際上,
          其實現(xiàn)原理是在JVM 中維護一個Map,這個Map的key 就是當(dāng)前的線程對象,而value則是
          線程通過ThreadLocal.set方法保存的對象實例。當(dāng)線程調(diào)用ThreadLocal.get方法時,
          ThreadLocal會根據(jù)當(dāng)前線程對象的引用,取出Map中對應(yīng)的對象返回。
          這樣,ThreadLocal通過以各個線程對象的引用作為區(qū)分,從而將不同線程的變量隔離開
          來。
          回到上面的例子,通過應(yīng)用ThreadLocal 機制,線程A 的session 實例只能為線程A
          所用,同樣,其他線程的session實例也各自從屬于自己的線程。這樣,我們就實現(xiàn)了線程安
          全的Session共享機制。
          Hibernate官方開發(fā)手冊的示例中,提供了一個通過ThreadLocal維護Session的好
          榜樣:
          public class HibernateUtil {
          private static final SessionFactory sessionFactory;
          static {
          try {
          // Create the SessionFactory
          sessionFactory = new
          Configuration().configure().buildSessionFactory();
          } catch (HibernateException ex) {
          throw new RuntimeException(
          "Configuration problem: " + ex.getMessage(),
          ex
          );
          }
          }
          public static final ThreadLocal session = new ThreadLocal();
          public static Session currentSession() throws HibernateException
          {
          Session s = (Session) session.get();
          // Open a new Session, if this Thread has none yet
          if (s == null) {
          s = sessionFactory.openSession();
          session.set(s);
          }
          return s;
          }
          public static void closeSession() throws HibernateException {
          Session s = (Session) session.get();
          session.set(null);
          if (s != null)
          s.close();
          }
          }
          在代碼中,只要借助上面這個工具類獲取Session 實例,我們就可以實現(xiàn)線程范圍內(nèi)的
          Session 共享,從而避免了在線程中頻繁的創(chuàng)建和銷毀Session 實例。不過注意在線程結(jié)束
          時關(guān)閉Session。
          同時值得一提的是,新版本的Hibernate在處理Session的時候已經(jīng)內(nèi)置了延遲加載機
          制,只有在真正發(fā)生數(shù)據(jù)庫操作的時候,才會從數(shù)據(jù)庫連接池獲取數(shù)據(jù)庫連接,我們不必過于擔(dān)
          心Session的共享會導(dǎo)致整個線程生命周期內(nèi)數(shù)據(jù)庫連接被持續(xù)占用。
          上面的HibernateUtil類可以應(yīng)用在任何類型的Java程序中。特別的,對于Web程序
          而言,我們可以借助Servlet2.3規(guī)范中新引入的Filter機制,輕松實現(xiàn)線程生命周期內(nèi)的
          Session管理(關(guān)于Filter的具體描述,請參考Servlet2.3規(guī)范)。
          Filter的生命周期貫穿了其所覆蓋的Servlet(JSP也可以看作是一種特殊的Servlet)
          及其底層對象。Filter在Servlet被調(diào)用之前執(zhí)行,在Servlet調(diào)用結(jié)束之后結(jié)束。因此,
          在Filter 中管理Session 對于Web 程序而言就顯得水到渠成。下面是一個通過Filter 進
          行Session管理的典型案例:
          public class PersistenceFilter implements Filter
          {
          protected static ThreadLocal hibernateHolder = new ThreadLocal();
          public void doFilter(ServletRequest request, ServletResponse
          response, FilterChain chain)
          throws IOException, ServletException
          {
          hibernateHolder.set(getSession());
          try
          {
          ……
          chain.doFilter(request, response);
          ……
          }
          finally
          {
          Session sess = (Session)hibernateHolder.get();
          if (sess != null)
          {
          hibernateHolder.set(null);
          try
          {
          sess.close();
          }
          catch (HibernateException ex) {
          throw new ServletException(ex);
          }
          }
          }
          }
          ……
          }
          通過在doFilter中獲取和關(guān)閉Session,并在周期內(nèi)運行的所有對象(Filter鏈中其
          余的Filter,及其覆蓋的Servlet 和其他對象)對此Session 實例進行重用,保證了一個
          Http Request處理過程中只占用一個Session,提高了整體性能表現(xiàn)。
          在實際設(shè)計中,Session的重用做到線程級別一般已經(jīng)足夠,企圖通過HttpSession實
          現(xiàn)用戶級的Session重用反而可能導(dǎo)致其他的問題。凡事不能過火,Session重用也一樣。J
          編后贅言
          Hibernate是一個優(yōu)秀的ORM實現(xiàn),不過請注意,它只是一個ORM
          實現(xiàn)而已,也不能保證是最優(yōu)秀的。
          筆者使用過的ORM 實現(xiàn)中,Apache OJB、Oracle TopLink、IBatis
          和Jaxor都給筆者留下了深刻映像,是否選擇Hibernate作為持久層實現(xiàn),
          需要結(jié)合實際情況考慮(在很多情況下,比如對遺留系統(tǒng)的改造項目中、
          ibatis可能更加合適)。
          合理的設(shè)計,冷靜的取舍是考量一個系統(tǒng)架構(gòu)師功底的最實際的標(biāo)
          準。常在網(wǎng)上看到對Hibernate 以及其他一些項目或者框架標(biāo)準的狂熱
          鼓吹,不禁想起自己三年前逢項目必定為EJB搖旗吶喊的樣子。
          設(shè)計需要用時間來沉淀,建筑、服裝都是如此,軟件產(chǎn)品也一樣……
          posted on 2006-08-22 09:03 Lizzie 閱讀(1460) 評論(0)  編輯  收藏 所屬分類: 專業(yè)積木

          <2006年8月>
          303112345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          常用鏈接

          留言簿(1)

          隨筆分類

          隨筆檔案

          文章分類

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 涡阳县| 遵义市| 肥乡县| 大埔区| 株洲县| 大余县| 宜宾市| 宁津县| 永安市| 崇明县| 原阳县| 溆浦县| 大英县| 湘潭县| 扶沟县| 桐乡市| 保山市| 陆河县| 永康市| 霍城县| 合阳县| 潢川县| 屯留县| 棋牌| 祁阳县| 宝应县| 武邑县| 林芝县| 兴国县| 吉隆县| 常熟市| 北碚区| 米林县| 台南县| 姜堰市| 图们市| 龙门县| 凤凰县| 齐河县| 于都县| 乌审旗|