Hibernate事務和併發控制

Hibernate事務和併發控制
                                           ++YONG原創,轉載請註明
1.    事務介紹:
1.1.       事務的定義:
事務就是指做爲單個邏輯工做單元執行的一組數據操做,這些操做要麼必須所有成功,要麼必須所有失敗,以保證數據的一致性和完整性。
1.2.       事務具備ACID屬性:
原子性(Atomic):事務由一個或多個行爲綁在一塊兒組成,好像是一個單獨的工做單元。原子性確保在事務中的全部操做要麼都發生,要麼都不發生。
一致性(Consistent):一旦一個事務結束了(無論成功與否),系統所處的狀態和它的業務規則是一致的。即數據應當不會被破壞。
隔離性(Isolated):事務應該容許多個用戶操做同一個數據,一個用戶的操做不會和其餘用戶的操做相混淆。
持久性(Durable):一旦事務完成,事務的結果應該持久化。
事務的ACID特性是由關係數據庫管理系統(RDBMS)來實現的。
o 數據庫管理系統採用日誌來保證事務的原子性、一致性和持久性。日誌記錄了事務對數據庫所作的更新,若是某個事務在執行過程當中發生錯誤,就能夠根據日誌,撤銷事務對數據庫已作的更新,使數據庫退回到執行事務前的初始狀態。
o 數據庫管理系統採用鎖機制來實現事務的隔離性。當多個事務同時更新數據庫相同的數據時,只容許持有鎖的事務能更新該數據,其餘事務必須等待,直到前一個事務釋放了鎖,其餘事務纔有機會更新該數據。
2.    數據庫事務聲明:
數據庫系統的客戶程序只要向數據庫系統聲明瞭一個事務,數據庫系統就會自動保證事務的ACID特性。在JDBC API中,java.sql.Connection類表明一個數據庫鏈接。它提供瞭如下方法控制事務:
1.         setAutoCommit(Boolean autoCommit):設置是否自動提交事務。
2.         commit():提交事務。
3.         rollback():撤銷事務。
2.1.       JDBC API聲明事務的示例代碼以下:
Connection = null;
PreparedStatement pstmt = null;
try{
con = DriverManager.getConnection(dbUrl, username, password);
//設置手工提交事務模式
con.setAutoCommit(false);
pstmt = ……;
pstmt.executeUpdate();
//提交事務
con.commit();
}catch(Exception e){
//事務回滾
con.rollback();
…..
} finally{
    …….
}
       Hibernate JDBC 進行了輕量級的對象封裝, Hibernate 自己在設計時並不具有事務處理功能,平時所用的 Hibernate 的事務,只是將底層的 JDBCTransaction 或者 JTATransaction 進行了一下封裝,在外面套上 Transaction Session 的外殼,其實底層都是經過委託底層的 JDBC JTA 來實現事務的調度功能。
2.2.       Hibernate中使用JDBC事務:
要在 Hibernate 中使用事務,能夠配置 Hibernate 事務爲 JDBCTransaction 或者 JTATransaction ,這兩種事務的生命週期不同,能夠在 hibernate.cfg.xml 中指定使用的是哪種事務。如下配置爲使用 JDBC 事務。注:若是不進行配置, Hibernate 也會默認使用 JDBC 事務。
 
<session-factory>
……
<property name="hibernate.transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory
</property>
……
</session-factory>
Hibernate 使用JDBC transaction處理方式以下所示:
Transaction tx = null;
try {
    tx = sess.beginTransaction();
 
    // do some work
    ...
 
    tx.commit();
}
catch (RuntimeException e) {
    if (tx != null) tx.rollback();
    throw e; // or display error message
}
finally {
    sess.close();
}
 
2.3.       Hibernate中使用JTA事務:
JTA(java Transaction API) 是事務服務的 JavaEE 解決方案。本質上,它是描述事務接口的 JavaEE 模型的一部分。
JTA 具備的 3 個接口: UserTransaction 接口、 TransactionManager 接口和 Transaction 接口,這些接口共享公共的事務操做。 UserTransaction 可以執行事務劃分和基本的事務操做, TransactionManager 可以執行上下文管理。
在一個具備多個數據庫的系統中,可能一個程序將會調用幾個數據庫中的數據,須要一種分佈事務,或者準備用 JTA 來管理 Session 的長事務,那麼就須要使用 JTATransaction
hibernate.cfg.xml 中配置 JTA 事務管理:
<session-factory>
……
<property name="hibernate.transaction.factory_class">
org.hibernate.transaction.JTATransactionFactory
</property>
……
</session-factory>
下面是一個實際應用的 JTA 示例:
// BMT(bean管理事務) idiom with getCurrentSession()
try {
    UserTransaction tx = (UserTransaction)new InitialContext()
                            .lookup("java:comp/UserTransaction");
 
    tx.begin();
 
    // Do some work on Session bound to transaction
    factory.getCurrentSession().load(...);
    factory.getCurrentSession().persist(...);
 
    tx.commit();
}
catch (RuntimeException e) {
    tx.rollback();
    throw e; // or display error message
}
在CMT方式下,事務聲明是在session bean的部署描述符中,而不須要編程。 所以,代碼被簡化爲:
// CMT idiom
Session sess = factory.getCurrentSession();
 
// do some work
...
 
3.    多個事務併發引發的問題:
3.1.         第一類丟失更新:撤消一個事務時,把其它事務已提交的更新的數據覆蓋了。
3.2.         髒讀:一個事務讀到另外一個事務未提交的更新數據。
3.3.         幻讀:一個事務執行兩次查詢,但第二次查詢比第一次查詢多出了一些數據行。
3.4.         不可重複讀:一個事務兩次讀同一行數據,但是這兩次讀到的數據不同。
3.5.         第二類丟失更新:這是不可重複讀中的特例,一個事務覆蓋另外一個事務已提交的更新數據。
4.    事務隔離級別:
爲了解決多個事務併發會引起的問題。數據庫系統提供了四種事務隔離級別供用戶選擇。
o Serializable:串行化。隔離級別最高
o Repeatable Read:可重複讀。
o Read Committed:讀已提交數據。
o Read Uncommitted:讀未提交數據。隔離級別最差。
數據庫系統採用不一樣的鎖類型來實現以上四種隔離級別,具體的實現過程對用戶是透明的。用戶應該關心的是如何選擇合適的隔離級別。
對於多數應用程序,能夠優先考慮把數據庫系統的隔離級別設爲Read Committed,它可以避免髒讀,並且具備較好的併發性能。
每一個數據庫鏈接都有一個全局變量@@tx_isolation,表示當前的事務隔離級別。 JDBC 數據庫鏈接使用數據庫系統默認的隔離級別。在Hibernate的配置文件中能夠顯示地設置隔離級別。每一種隔離級別對應着一個正整數。
Read Uncommitted: 1
Read Committed: 2
Repeatable Read: 4
Serializable: 8
在hibernate.cfg.xml中設置隔離級別以下:
    <session-factory>
<!-- 設置JDBC的隔離級別 -->
<property name="hibernate.connection.isolation">2</property>
</session-factory>
設置以後,在開始一個事務以前,Hibernate將爲從鏈接池中得到的JDBC鏈接設置級別。須要注意的是,在受管理環境中, 若是 Hibernate 使用的數據庫鏈接來自於應用服務器提供的數據源,Hibernate 不會改變這些鏈接的事務隔離級別。在這種狀況下,應該經過修改應用服務器的數據源配置來修改隔離級別。
5.    併發控制:
當數據庫系統採用Red Committed隔離級別時,會致使不可重複讀和第二類丟失更新的併發問題,在可能出現這種問題的場合。能夠 在應用程序中採用悲觀鎖或樂觀鎖來避免這類問題。
5.1.       樂觀鎖(Optimistic Locking)
樂觀鎖假定當前事務操縱數據資源時,不會有其餘事務同時訪問該數據資源,所以不做數據庫層次上的鎖定。爲了維護正確的數據,樂觀鎖使用應用程序上的版本控制(由程序邏輯來實現的)來避免可能出現的併發問題。
惟一可以同時保持高併發和高可伸縮性的方法就是使用帶版本化的樂觀併發控制。版本檢查使用版本號、 或者時間戳來檢測更新衝突(而且防止更新丟失)。
5.1.1.        使用版本檢查(<version>)
Hibernate中經過版本號檢查來實現後更新爲主,這也是Hibernate推薦的方式。在數據庫表中加入一個version(版本)字段,在讀取數據時連同版本號一塊兒讀取,並在更新數據時比較版本號與數據庫表中的版本號,若是等於數據庫表中的版本號則予以更新,並遞增版本號,若是小於數據庫表中的版本號就拋出異常。
使用<version>進行版本控制的步驟:
1)      在持久化類中定義一個表明版本號的屬性:
package org.qiujy.domain.versionchecking;
 
import java.util.Date;
 
public class Product implements java.io.Serializable{
       private Long id ;
       /** 版本號 */
       private int version;
       private String name; //產品名
       private String description; //描述--簡介
       private Double unitCost; //單價
       private Date pubTime; //生產日期
      
       public Product(){}
 
       //如下爲getter()和setter()方法
}
 
2)      在Product.hbm.xml文件中用<version>元素來創建Product類的version屬性與表中version字段的映射。
3)      Hibernate在其數據庫訪問引擎中內置了樂觀鎖定實現,默認也是選擇version方式做爲Hibernate樂觀鎖定實現機制。因此,在配置文件及程序中能夠不做其它設置。按往常同樣寫操做代碼。
package org.qiujy.domain.versionchecking;
 
import java.util.Date;
 
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.qiujy.common.HibernateSessionFactory;
 
public class TestVersionChecking {
      
       public static void main(String[] args) {
              Product prod = new Product();
              prod.setName("IBM thinkPad T60");
              prod.setUnitCost(new Double(26000.00));
              prod.setDescription("筆記本電腦");
              prod.setPubTime(new Date());
             
              //test start.......
              Session session = HibernateSessionFactory.getSession();
              Transaction tx =null;
              try{
                     tx = session.beginTransaction();
                    
                     session.save(prod);
                
                    tx.commit();
              }catch(HibernateException e){
                     if(tx != null){
                            tx.rollback();
                     }
                     e.printStackTrace();
              }finally{
                     HibernateSessionFactory.closeSession();
              }
       
//進行更新 測試..
              prod.setDescription("新款的");
             
              Session session2 = HibernateSessionFactory.getSession();
              Transaction tx2 =null;
              try{
                     tx2 = session2.beginTransaction();
                    
                     session2.update(prod);
               
                    tx2.commit();
              }catch(HibernateException e){
                     if(tx2 != null){
                            tx2.rollback();
                     }
                     e.printStackTrace();
              }finally{
                     HibernateSessionFactory.closeSession();
              }
       }
}
新增數據時產生的SQL是:
insert into products (version, name, description, unitCost, pubTime)
    values(?, ?, ?, ?, ?)
程序無需爲Product對象的version屬性顯示賦值,當持久化一個Product對象時,Hibernate會自動爲它賦初始值爲0。
更新數據時產生的SQL是:
    update
        products
    set
        version=?,
        name=?,
        description=?,
        unitCost=?,
        pubTime=?
    where
        id=?
        and version=?
當Hibernate更新一個Product對象,會根據它的id和version屬性到相應的數據庫表中定位匹配的記錄,若是存在這條匹配的記錄,就更新記錄,而且把version字段的值加1。若找不到匹配的記錄,此時Hibernate會拋出StaleObjectStateException。
 
須要注意的是,因爲樂觀鎖定是使用系統中的程序來控制,而不是使用數據庫中的鎖定機制,於是若是有人故意自行更新版本信息來超過檢查,則鎖定機制就無效。因此建議把持久化類中的version的get方法設置爲private的。
5.1.2.        使用時間戳(<timestamp>)
跟版本檢查的用法類似。再也不多說。
5.2.       悲觀鎖(Pessimistic Locking)
悲觀鎖假定當前事務操縱數據資源時,確定還會有其餘事務同時訪問該數據資源,爲了不當前事務的操做受到干擾,先鎖定資源。儘管悲觀鎖可以防止丟失更新和不可重複讀這類併發問題,可是它影響併發性能,所以應該很謹慎地使用悲觀鎖。
相關文章
相關標籤/搜索