4.4 Hibernate高級功能


1.Hibernate批量處理

    在Hibernate應用中,有兩種批量處理方法:一種是經過Hibernate的緩存另外一種是繞過Hibernate,直接調用JDBC API來處理java

 

1)批量插入

(1)經過Hibernate的緩存進行批量插入數據庫

    使用這種方法時,首先要在Hibernate的配置文件hibernate.cfg.xml中設置批量尺寸屬性hibernate.jdbc.batch_size且最好關閉Hibernate的二級緩存以提升效率數組

 

例如:緩存

<hibernate-configuration>
    <session-factory>
         …
        <property name="hibernate.jdbc.batch_size">50</property>                         // 設置批量尺寸
        <property name="hibernate.cache.use_second_level_cache">false</property>         // 關閉二級緩存
    </session-factory>
</hibernate-configuration>

    下面以4.2.1節的例子中的課程進行批量插入爲例,說明批量插入操做的具體過程,這裏假設批量插入500個課程到數據中:session

Session session=HibernateSessionFactory.getSession();
Transaction ts=session.beginTransaction();
for(int i=0;i<500;i++){
    Kcb kcb=new Kcb();
    // 這裏設置課程號爲i,在實際應用中應該是被插入的課程對象
    // 已經放在集合或數組中,這裏只要取出
    kcb.setKch(i+"");  
    session.save(kcb);
    if(i%50==0){          // 以50個課程爲一個批次向數據庫提交,此值應與配置的批量尺寸一致
        session.flush();  // 將該批量數據當即插入數據庫中
        session.clear();  // 清空緩存區,釋放內存供下批數據使用
    }
}
ts.commit();
HibernateSessionFactory.closeSession();

(2)繞過Hibernate直接調用JDBC進行插入併發

    因爲Hibernate只是對JDBC進行了輕量級的封裝,所以徹底能夠繞過Hibernate直接調用JDBC進行批量插入。所以上例能夠改爲以下代碼:分佈式

Session session=HibernateSessionFactory.getSession();
Transaction ts=session.beginTransaction();
Connection conn=session.connection();
try {
    PreparedStatement  stmt=conn.prepareStatement("insert into KCB(KCH) values(?)");
    for (int i=0; i < 500; i++) {
        stmt.setString(1, i+"");
        stmt.addBatch();                  // 添加到批處理命令中
    }
    stmt.executeBatch();                   // 執行批處理任務
} catch (SQLException e) {
    e.printStackTrace();
}
ts.commit();
HibernateSessionFactory.closeSession();

2)批量更新

(1)由Hibernate直接進行批量更新性能

    爲了使Hibernate的HQL直接支持update/delete的批量更新語法,首先要在Hibernate的配置文件hibernate.cfg.xml設置HQL/SQL查詢翻譯器屬性hibernate.query.factory_classspa

<hibernate-configuration>       
    <session-factory>
    ……
        <propertyname="hibernate.query.factory_class">org.hibernate.hql.ast.ASTQueryTranslatorFactory</property>
    </session-factory>
<hibernate-configuration>


下面使用HQL批量更新把課程表中的XS修改成30。因爲這裏是用Hibernate操做,故HQL要用類對象及其屬性。.net

Session session=HibernateSessionFactory.getSession();
Transaction ts=session.beginTransaction();
//在HQL查詢中使用update進行批量更新
Query query=session.createQuery("update Kcb set xs=30");
query.executeUpdate();
ts.commit();
HibernateSessionFactory.closeSession();

(2)繞過Hibernate調用JDBC進行批量更新

    因爲這裏是直接操做數據庫,故要操做對應表,而不是類。

Session session=HibernateSessionFactory.getSession();
Transaction ts=session.beginTransaction();
Connection conn=session.connection();
try {
    Statement stmt=conn.createStatement();
    //調用JDBC的update進行批量更新
    stmt.executeUpdate("update KCB set XS=30");
} catch (SQLException e) {
    e.printStackTrace();
}
ts.commit();
HibernateSessionFactory.closeSession();

3)批量刪除

(1)由Hibernate直接進行批量刪除

    與批量更新同樣,爲了使Hibernate的HQL直接支持update/delete的批量刪除語法,首先要在Hibernate的配置文件hibernate.cfg.xml中設置HQL/SQL查詢翻譯器屬性hibernate.query.factory_class

<hibernate-configuration>       
    <session-factory>
    ……
        <propertyname="hibernate.query.factory_class">org.hibernate.hql.ast.ASTQueryTranslatorFactory</property>
    </session-factory>
<hibernate-configuration>

    下面將使用HQL批量刪除課程表中課程號大於200的課程。

Session session=HibernateSessionFactory.getSession();
Transaction ts=session.beginTransaction();
//在HQL查詢中使用delete進行批量刪除
Query query=session.createQuery("delete Kcb where kch>200");
query.executeUpdate();
ts.commit();
HibernateSessionFactory.closeSession();

(2)繞過Hibernate調用JDBC進行批量刪除

    一樣刪除課程表中課程號大於200的課程。

Session session=HibernateSessionFactory.getSession();
Transaction ts=session.beginTransaction();
Connection conn=session.connection();
try {
    Statement stmt= conn.createStatement();
    //調用JDBC的delete進行批量刪除
    stmt.executeUpdate("delete from KCB where KCH>200");       
} catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
ts.commit();
HibernateSessionFactory.closeSession();

2.實體對象生命週期

    是Hibernate應用的一個關鍵概念。這裏的實體對象,特指Hibernate O/R映射關係中的域對象(即O/R中的O)

    實體對象的生命週期有如下3種狀態。

1)transient(瞬時態)

    瞬時態,即實體對象在內存中的存在與數據庫中的記錄無關。以下面的代碼:

Student stu=new Student();
stu.setSnumber("081101");
stu.setSname("李方方");
stu.setSage(21);

2)persisent(持久態)

    在這種狀態下,實體對象的引用被歸入Hibernate實體容器中加以管理處於持久狀態的對象,其變動將由Hibernate固化到數據庫中。例以下面的代碼。

Student stu=new Student();
Student stu1=new Student();
stu.setSnumber("081101");
stu.setSname("李方方");
stu.setSage(21);
stu1.setSnumber("081102");
stu1.setSname("程明");
stu1.setSage(22);                                        // 到此爲止,stu和stu1均處於瞬時態
Transaction tx=session.beginTransaction();
session.save(stu); // 經過save()方法,stu對象轉換爲持久態,由Hibernate歸入實體管理容器,而stu1仍然處於瞬時態
// 事務提交以後,數據庫表中插入一條學生的記錄,對於stu1則無任何操做
tx.commit();
Transaction tx2=session.beginTransaction();
stu.setSname("李方");
stu1.setSname("程明明");
// 雖然這個事務中沒有顯示調用session.save()方法保存stu對象,可是因爲stu爲持久態,將自動被固化到數據庫,所以數據庫的學號爲「081101」 學生記錄的姓名已被更改成「李方」,此時stu1仍然是一個普通Java對象,對數據庫未產生任何影響
tx2.commit();

處於瞬時態的對象,能夠經過Session的save()方法轉換成持久狀態。一樣,若是一個實體對象由Hibernate加載,那麼,它也處於持久狀態。例以下面的

// 由Hibernate返回的持久對象
Student stu=(Student)session.load(Student.class,new Integer(1));

3)Detached(脫管狀態)

    處於持久態的對象,其對應的Session實例關閉以後,此對象就處於脫管狀態Session實例能夠看作是持久對象的宿主,一旦此宿主失效,其從屬的持久對象進入脫管狀態。以下面的代碼:

Student stu=new Student();// stu處於瞬時態
Student stu1=new Student();
stu.setSnumber("081101");
stu.setSname("李方方");
stu.setSage(21);
stu1.setSnumber("081102");
stu1.setSname("程明");
stu1.setSage(22);
Transaction tx=session.beginTransaction();
session.save(stu);// stu對象由Hibernate歸入管理容器,處於持久狀態
tx.commit();
session.close();// stu對象狀態爲脫管態,由於與其關聯的session已經關閉

    瞬時狀態的對象與庫表中的數據庫缺少對應關係;而託管狀態的對象,卻在庫表中存在相應的記錄,只不過因爲其脫離Session這個數據庫操做平臺,其狀態改變沒法更新到數據庫表中

3.Hibernate事務管理

    事務是數據庫併發控制不可分隔的基本工做單位具備原子性、一致性、隔離性和持久性的特色

    事務(Transcation)是工做中的基本邏輯單位,能夠用於確保數據庫可以被正確修改,避免只修改了一部分致使數據不完整或者在修改時受到用戶干擾

1)基於JDBC的事務管理

    Hibernate是JDBC的輕量級封裝,自己並不具有事務管理能力在事務管理層,Hibernate將其委託給底層的JDBC或JTA以實現事務管理和調度功能

    在JDBC的數據庫操做中,一項事務是由一條或多條表達式組成的不可分割的工做單元,經過提交commit()或回滾rollback()來結束事務的操做。

    在JDBC中,事物默認是自動提交。也就是說,一條更新表達式表明一項事物操做。操做成功後,系統將自動調用commit()提交。不然將調用rollback()回滾

    在JDBC中,能夠經過調用setAutoCommit(false)禁止自動提交,以後就能夠把多個數據庫操做的表達式做爲一個事物,在操做完成後調用commit()進行總體提交

    將事務管理委託給JDBC進行處理是最簡單的實現方式Hibernate對於JDBC事務的封裝也比較簡單。以下面的代碼:

Session session=sessionFactory.openSession();
Transaction tx=session.beginTransaction();
session.save(room);
tx.commit();


從JDBC層面而言,上面的代碼實際上對應着

Connection cn=getConnection;
cn.setAutoCommit(false);
// JDBC調用相關的SQL語句
cn.commit();

    在sessionFactory.open()語句中,Hibernate會初始化數據庫鏈接。與此同時,將其AutoCommit()設爲關閉狀態(false)。所以在調用commit()以前,下面的代碼不會對數據庫產生任何效果:

session session=session.Factory.openSession();
session.save(room);
session.close();

    若是要使代碼真正做用到數據庫,必須顯示地調用Transaction指令

Session session =sessionFactory.openSession();
Transaction tx=sessio.beginTransaction();
session.save(room);
tx.commit();
session.close();

2)基於JTA的事務管理概念

    JTA(Java Transaction API)是由Java EE Transaction Manager去管理的事務。其最大的特色是調用UserTransaction接口的begin、commit和rollback方法來完成事務範圍的界定、事務的提交和回滾。JTA能夠實現統一事務對應不一樣的數據庫

    JTA主要用於分佈式的多個數據源的兩階段提交的事務而JDBC的Connection提供單個數據源的事務。後者由於只涉及一個數據源,因此其事務能夠由數據庫本身單獨實現。而JTA事務由於其分佈式和多數據源的特性,不可能由任何一個數據源實現事務。所以,JTA中的事務是由「事務管理器」實現的。它會在多個數據源之間統籌事務,具體使用的技術就是所謂的「兩階段提交」

 

3)鎖

    業務邏輯的實現過程當中,每每須要保證數據訪問的排他性。如在金融系統的日終結算處理中,但願對某個結算時間點的數據進行處理,而不但願在結算過程當中(多是幾秒,也多是幾個小時),數據再發生變化。此時,須要經過一些機制來保證這些數據在某個操做過程當中不會被外界修改,這樣的機制就是所謂的「鎖」,即給選定的目標數據上鎖,使其沒法被其餘程序修改。

    Hibernate支持兩種鎖機制,悲觀鎖(Pessimistic Locking)樂觀鎖(Optimistic Locking)。

(1)悲觀鎖,它指的是對數據被外界修改持保守態度,所以,在整個數據處理過程當中,將數據處於鎖定狀態。悲觀鎖的實現,每每依靠數據庫提供的鎖機制

(2)樂觀鎖,悲觀鎖大多數狀況下依靠數據庫的鎖機制實現,以保證操做最大程度的獨佔性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷每每沒法承受。

    樂觀鎖,大可能是基於數據版本( Version )記錄機制實現。讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然就報錯。

    對於上面修改用戶賬戶信息的例子而言,假設數據庫中賬戶信息表中有一個version 字段,當前值爲 1 ;而當前賬戶餘額字段( balance )爲 $100 。

    操做員 A 此時將其讀出( version=1 ),並從其賬戶餘額中扣除 $50( $100-$50 )。

    在操做員 A 操做的過程當中,操做員 B 也讀入此用戶信息( version=1 ),並從其賬戶餘額中扣除$20 ( $100-$20 )。

    操做員 A 完成了修改工做,將數據版本號加一( version=2 ),連同賬戶扣除後餘額( balance=$50 ),提交至數據庫更新,此時因爲提交數據版本大於數據庫記錄當前版本,數據被更新,數據庫記錄 version 更新爲 2 。

    操做員 B 完成了操做,也將版本號加一( version=2 )試圖向數據庫提交數據( balance=$80 ),但此時比對數據庫記錄版本時發現,操做員 B 提交的數據版本號爲 2 ,數據庫記錄當前版本也爲 2 ,不知足 「 提交版本必須大於記錄當前版本才能執行更新 「 的樂觀鎖策略,所以,操做員 B 的提交被駁回。這樣,就避免了操做員 B 用基於 version=1 的舊數據修改的結果覆蓋操做員 A 的操做結果的可能

    須要注意的是,樂觀鎖機制每每基於系統中的數據存儲邏輯,所以也具有必定的侷限性,如在上例中,因爲樂觀鎖機制是在咱們的系統中實現,來自外部系統的用戶餘額更新操做不受咱們系統的控制所以可能會形成髒數據被更新到數據庫中


附:目錄《JavaEE基礎實用教程》筆記說明

相關文章
相關標籤/搜索