<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    進行中  
    。。。
    日歷
    <2006年8月>
    303112345
    6789101112
    13141516171819
    20212223242526
    272829303112
    3456789
    統(tǒng)計
    • 隨筆 - 13
    • 文章 - 1
    • 評論 - 3
    • 引用 - 0

    導(dǎo)航

    常用鏈接

    留言簿(1)

    隨筆檔案

    文章檔案

    相冊

    link

    搜索

    •  

    最新隨筆

    最新評論

    閱讀排行榜

    評論排行榜

     
    Hibernate 開發(fā)指南
    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
    準(zhǔn)備工作..........................................................................................................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
    準(zhǔn)備工作
    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)準(zhǔn)的部分是我們進行配置的內(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ù)庫表映射
    文件。可配置的項目包括:
    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ù)庫操作做好
    準(zhǔn)備。
    配置文件應(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());
    }
    }
    /**
    * 對象讀取(Select)測試
    * 請保證運行之前數(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ū)ο蟮乃枷耄瑫r也大大
    提高了開發(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ǔ)信息以完成初始化工作,為后繼操作做好準(zhǔn)
    備。這些屬性在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é)者常常混淆inverse和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ù)庫提供的分頁(部分讀取)模式往往各不相同,也帶來了數(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)
    準(zhǔn)。常在網(wǎng)上看到對Hibernate 以及其他一些項目或者框架標(biāo)準(zhǔn)的狂熱
    鼓吹,不禁想起自己三年前逢項目必定為EJB搖旗吶喊的樣子。
    設(shè)計需要用時間來沉淀,建筑、服裝都是如此,軟件產(chǎn)品也一樣……
    posted on 2006-08-18 11:38 kongjia 閱讀(1312) 評論(0)  編輯  收藏

    只有注冊用戶登錄后才能發(fā)表評論。


    網(wǎng)站導(dǎo)航:
     
     
    Copyright © kongjia Powered by: 博客園 模板提供:滬江博客
    主站蜘蛛池模板: 特级做A爰片毛片免费69| 性短视频在线观看免费不卡流畅| 日本人护士免费xxxx视频| 久久久久亚洲av无码专区| 国内精品免费视频精选在线观看 | 日韩亚洲国产综合久久久| 亚洲欧美黑人猛交群| 永久黄网站色视频免费直播 | 精品免费久久久久久成人影院| 亚洲理论在线观看| 我们的2018在线观看免费高清| 亚洲综合日韩中文字幕v在线| 免费无码一区二区三区| 91精品国产亚洲爽啪在线影院| 一级成人a毛片免费播放| 亚洲天天在线日亚洲洲精| 2019中文字幕在线电影免费| 亚洲国产片在线观看| 性色av免费观看| 羞羞漫画小舞被黄漫免费| 区三区激情福利综合中文字幕在线一区亚洲视频1| 亚洲av永久无码一区二区三区| 免费无码看av的网站| 美女视频免费看一区二区| 亚洲美女在线国产| 久久免费视频观看| 亚洲国产精品成人综合久久久 | 嫩草影院在线免费观看| 久久亚洲精品无码av| 在线精品亚洲一区二区三区| 国内精品免费视频精选在线观看 | 久久精品亚洲一区二区三区浴池| 黄色网址免费观看| 在线观看亚洲电影| 国产亚洲综合成人91精品| 青青青国产在线观看免费| 美女视频免费看一区二区| 亚洲男人的天堂在线播放| 毛片免费全部免费观看| 一级做a爱过程免费视| 亚洲精品免费在线|