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

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

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

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


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

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


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


    Hibernate Quick Start

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


    構建Hibernate 基礎代碼
    Hibernate基礎代碼包括:
    1. POJO
    POJO 在Hibernate 語義中理解為數據庫表所對應的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 從本質上來講是一種“對象-關系型數據映射”(Object Relational
    Mapping 簡稱ORM)。前面的POJO在這里體現的就是ORM中Object層的語義,
    而映射(Mapping)文件則是將對象(Object)與關系型數據(Relational)相關聯
    的紐帶,在Hibernate中,映射文件通常以“.hbm.xml”作為后綴。
    構建Hibernate基礎代碼通常有以下途徑:
    1. 手工編寫
    2. 直接從數據庫中導出表結構,并生成對應的ORM文件和Java 代碼。
    這是實際開發中最常用的方式,也是這里所推薦的方式。
    通過直接從目標數據庫中導出數據結構,最小化了手工編碼和調整的可能性,從而
    最大程度上保證了ORM文件和Java 代碼與實際數據庫結構相一致。
    3. 根據現有的Java 代碼生成對應的映射文件,將Java 代碼與數據庫表相綁定。
    通過預先編寫好的POJO 生成映射文件,這種方式在實際開發中也經常使用,特別
    是結合了xdoclet 之后顯得尤為靈活,其潛在問題就是與實際數據庫結構之間可能
    出現的同步上的障礙,由于需要手工調整代碼,往往調整的過程中由于手工操作的
    疏漏,導致最后生成的配置文件錯誤,這點需要在開發中特別注意。
    結合xdoclet,由POJO 生成映射文件的技術我們將在“高級特性”章節中進行探討。
    由數據庫產生基礎代碼
    通過Hibernate官方提供的MiddleGen for Hibernate 和Hibernate_Extension工具包,我
    們可以很方便的根據現有數據庫,導出數據庫表結構,生成ORM和POJO。
    1) 首先,將Middlegen-Hibernate軟件包解壓縮( 如解壓縮到C:\Middlegen\ )。
    2) 配置目標數據庫參數
    進入MiddleGen 目錄下的\config\database 子目錄,根據我們實際采用的數據庫打開
    對應的配置文件。如這里我們用的是mysql數據庫,對應的就是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"/>
    其中下劃線標準的部分是我們進行配置的內容,分別是數據url以及數據庫用
    戶名和密碼。
    3) 修改Build.xml
    修改MiddleGen 根目錄下的build.xml 文件,此文件是Middlegen-Hibernate 的Ant
    構建配置。Middlegen-Hibernate將根據build.xml 文件中的具體參數生成數據庫表映射
    文件。可配置的項目包括:
    a) 目標數據庫配置文件地址
    查找關鍵字 ”!ENTITY”,得到:
    <!DOCTYPE project [
    <!ENTITY database SYSTEM
    "file:./config/database/hsqldb.xml">
    ]>
    默認情況下,MiddleGen 采用的是hsqldb.xml,將其修改為我們所用的數據
    庫配置文件(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) 輸出目錄
    查找關鍵字“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) 對應代碼的Package name
    查找關鍵字“destination”,得到:
    <hibernate
    destination="${build.gen-src.dir}"
    package="${name}.hibernate"
    genXDocletTags="false"
    genIntergratedCompositeKeys="false"
    javaTypeMapper=
    "middlegen.plugins.hibernate.HibernateJavaTypeMapper"
    />
    可以看到,hibernate 節點package 屬性的默認設置實際上是由前面的
    Application Name (${name})和“.hibernate”組合而成,根據我們的需要,
    將其改為:
    <hibernate
    destination="${build.gen-src.dir}"
    package="org.hibernate.sample"
    genXDocletTags="true"
    genIntergratedCompositeKeys="false"
    javaTypeMapper=
    "middlegen.plugins.hibernate.HibernateJavaTypeMapper"
    />
    這里還有一個屬性genXDocletTags,如果設置為true,則生成的代碼將包含
    xdoclet tag,這為以后在開發過程中借助xdoclet進行映射調整提供了幫助。關
    于Hibernate的xdoclet使用,請參見“高級特性”中的相關內容。
    注意,如果使用的數據庫為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 已經配置完畢,在MiddleGen 根目錄下運行ant,就將出現
    MiddleGen的界面:
    可以看到,數據庫中的表結構已經導入到MiddleGen 的操作界面中,選定數據庫
    表視圖中的表元素,我們即可調整各個數據庫表的屬性。
    1 Domain Class Name
    對應POJO 的類名
    2 Key Generator
    主鍵產生器
    可選項說明:
    1) Assigned

    ② ③


    ⑥ ⑦



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



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

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

    常用鏈接

    留言簿(1)

    隨筆分類

    隨筆檔案

    文章分類

    搜索

    •  

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 亚洲日本一区二区一本一道 | 久久成人国产精品免费软件| 久久亚洲sm情趣捆绑调教| 在线观看特色大片免费视频| 黄页网站在线视频免费| 亚洲av无码国产精品色午夜字幕| 最近免费字幕中文大全视频| 亚洲人成电影网站免费| 精品一区二区三区无码免费视频| 天堂亚洲国产中文在线| 伊人久久大香线蕉亚洲五月天 | 免费毛片在线看片免费丝瓜视频| 污视频网站在线免费看| 亚洲人成网站在线播放影院在线 | 最近中文字幕国语免费完整| 亚洲国产区男人本色| 亚洲国产成人片在线观看| 免费观看一级毛片| 久久国产乱子伦精品免费不卡| 亚洲熟妇无码av另类vr影视| 亚洲AV永久无码精品一百度影院| 日韩电影免费在线观看视频| 国产亚洲玖玖玖在线观看| 亚洲欧洲∨国产一区二区三区| 在线免费观看视频你懂的| 亚洲精品一卡2卡3卡四卡乱码| 亚洲AV午夜成人片| 亚洲福利视频一区二区| 国产精品视频免费一区二区| 毛片在线全部免费观看| 男女交性无遮挡免费视频| 国产婷婷综合丁香亚洲欧洲| 亚洲伦另类中文字幕| 国产成人精品日本亚洲专区| 国产免费私拍一区二区三区| 国产成人福利免费视频| 日本免费高清视频| 亚欧洲精品在线视频免费观看| 亚洲AV成人精品一区二区三区| 精品久久久久久亚洲精品| 国产jizzjizz视频全部免费|