Spring的事務管理(一) Spring事務管理的實現,事務的屬性(隔離級別,傳播行為,只讀)

z1340954953發表於2018-07-16

Spring中事務的管理具體策略

Spring事務管理中涉及到的介面


事務管理器

Spring並不直接管理事務,而是提供了一個介面org.springframework.transaction.PlatformTransactionManager,具體的實現交由各個平臺jdbc,hibernate等等自己實現。

public abstract interface PlatformTransactionManager
{
  public abstract TransactionStatus getTransaction(TransactionDefinition paramTransactionDefinition)
    throws TransactionException;
  
  public abstract void commit(TransactionStatus paramTransactionStatus)
    throws TransactionException;
  
  public abstract void rollback(TransactionStatus paramTransactionStatus)
    throws TransactionException;
}

* jdbc事務管理器

如果應用程式中直接使用JDBC來進行持久化,DataSourceTransactionManager會為你處理事務邊界。

為了使用DataSourceTransactionManager,你需要使用如下的XML將其裝配到應用程式的上下文定義中:

  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
  </bean>

DataSourceTransactionManager是通過呼叫Connection來管理事務,通過呼叫Connection的commit方法提交事務,rollback方法回滾事務

* Hiberante事務

Hibernate持久化策略,利用HibernateTransactionManager進行事務管理。對於Hibernate3,需要在Spring上下文定義中新增如下的<bean>宣告:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
</bean>

sessionFactory屬性需要裝配一個Hibernate的session工廠,HibernateTransactionManager的實現細節是它將事務管理的職

責委託給org.hibernate.Transaction物件,而後者是從Hibernate Session中獲取到的。

當事務成功完成時,HibernateTransactionManager將會呼叫Transaction物件的commit()方法,反之,將會呼叫rollback()方法。

* JPA事務

Hibernate多年來一直是事實上的Java持久化標準,但是現在Java持久化API作為真正的Java持久化標準進入大家的視野。

如果你計劃使用JPA的話,那你需要使用Spring的JpaTransactionManager來處理事務。你需要在Spring中這樣配置JpaTransactionManager:

  <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

JpaTransactionManager只需要裝配一個JPA實體管理工廠(javax.persistence.EntityManagerFactory介面的任意實現)。

JpaTransactionManager將與由工廠所產生的JPA EntityManager合作來構建事務。

 Java原生API事務

如果你沒有使用以上所述的事務管理,或者是跨越了多個事務管理源(比如兩個或者是多個不同的資料來源),你就

需要使用JtaTransactionManager:

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManagerName" value="java:/TransactionManager" />
</bean>

JtaTransactionManager將事務管理的責任委託給javax.transaction.UserTransaction

和javax.transaction.TransactionManager對象,其中事務成功完成通過UserTransaction.commit()方法提交,事務

失敗通過UserTransaction.rollback()方法回滾。

事務屬性的定義

前面介紹Spring中通過PlatformTransactionManager的方法getTransaction(TransactionDefinition)獲取事務,類

TransactionDefinition定義了一些事務的基本屬性。

事務屬性可以理解成事務的一些基本配置,描述了事務策略如何應用到方法上。事務屬性包含了5個方面,如圖

介面TransactionDefinition定義事務的屬性

public interface TransactionDefinition {
    int getPropagationBehavior(); // 返回事務的傳播行為
    int getIsolationLevel(); // 返回事務的隔離級別,事務管理器根據它來控制另外一個事務可以看到本事務內的哪些資料
    int getTimeout();  // 返回事務必須在多少秒內完成
    boolean isReadOnly(); // 事務是否只讀,事務管理器能夠根據這個返回值進行優化,確保事務是隻讀的
} 

* 只讀

事務的第三個特性是它是否為只讀事務。如果事務只對後端的資料庫進行該操作,資料庫可以利用事務的只讀特性來進行一些特定的優化。通過將事務設定為只讀,資料庫會提供優化

* 事務超時

為了使應用程式很好地執行,事務不能執行太長的時間。因為事務可能涉及對後端資料庫的鎖定,所以長時間的事務會不必要的佔用資料庫資源。事務超時就是事務的一個定時器,在特定時間內事務如果沒有執行完畢,那麼就會自動回滾,而不是一直等待其結束。

* 回滾規則

這些規則定義了哪些異常會導致事務回滾而哪些不會。預設情況下,事務只有遇到執行期異常時才會回滾,而在遇到檢查型異常時不會回滾(這一行為與EJB的回滾行為是一致的) 

但是你可以宣告事務在遇到特定的檢查型異常時像遇到執行期異常那樣回滾。同樣,你還可以宣告事務遇到特定的異常不回滾,即使這些異常是執行期異常。

* 事務狀態

上面講到的呼叫PlatformTransactionManager介面的getTransaction()的方法得到的是TransactionStatus介面的一個實現,這個介面的內容如下:

public interface TransactionStatus{
    boolean isNewTransaction(); // 是否是新的事物
    boolean hasSavepoint(); // 是否有恢復點
    void setRollbackOnly();  // 設定為只回滾
    boolean isRollbackOnly(); // 是否為只回滾
    boolean isCompleted; // 是否已完成
} 
可以發現這個介面描述的是一些處理事務提供簡單的控制事務執行和查詢事務狀態的方法,在回滾或提交的時候需要應用對應的事務狀態。

* 事務的隔離級別

資料庫當同時存在兩個或者兩個以上的資料庫事務環境,存在一些併發的問題。

髒讀:一個事務讀取到另一個事務沒有提交的資料

不可重複讀:A事務讀取同一行記錄讀取兩次,第一次讀取一行記錄,期間B事務修改了這行記錄,並提交事務,A事務隨後讀取到的記錄和前一次不一致

幻讀:一個事務讀取兩次表,第一次去讀取表的記錄數,隨後另一個事務往表中新增了一行記錄,再次去讀表的記錄,發現多了一行記錄,就像是憑空產生的。

資料庫提供了四種隔離級別來解決?

read uncommitted: 未提交讀,會出現髒讀問題,是最低的隔離級別

read committed:提交讀,只有事務提交了,才能讀取,避免髒讀,但是一個事務中兩次讀取的一行資料不一致的問題,會出現,也就是會出現不可以重複讀問題。大多數資料庫預設的事務隔離級別

repeatedable read:重複讀,就是事務開啟了,執行讀取操作,不允許對這行資料的修改操作,避免了不可重複讀問題。

但是仍會出現幻讀問題,因為repeatedable read 是禁止了update操作,仍然可以insert。

serializable:序列。一個事務一個事務,排隊執行,隔離級別最高,不會出現幻讀問題,但是效能低。

事務的隔離級別
隔離級別/專案髒讀不可重複讀幻讀
read uncommitted
read committed不會
repeatable read不會不會
Serializable不會不會不會

* 事務的傳播行

       傳播行為指的是,方法之間的呼叫問題。在大部分情況下,事務處理都應該是一次性的全部成功或失敗的。但是存在特殊情況,在批處理中,不能因為一個處理失敗就全部失敗,應該是每個批處理中的單元的事務都需要獨立開來。

事務的傳播行為
傳播行為解釋備註
PROPAGATION_REQUIRED如果存在一個事務,則支援當前事務。如果沒有事務則開啟事務Spring中預設的傳播行為
PROPAGATION_SUPPORTS如果存在一個事務,則支援當前事務。如果沒有事務,則非事務的執行 
PROPAGATION_MANDATORY如果已經存在一個事務,則支援當前事務。如果沒有一個活動的事務,則丟擲異常 
PROPAGATION_REQUIREDS_NEW總是開啟一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起在批處理,信用卡還款處理,給每一個卡建立一個獨立的事務,不會因為一個處理失敗,全部失敗
PROPAGATION_NOT_SUPPORTED總是非事務的執行,並掛起任何存在的事務 
PROPAGATION_NEVER總是非事務的執行,如果已經存在一個事務,則丟擲異常 
PROPAGATION_NESTED如果一個活動的事務存在,則執行在一個巢狀的事務中;如果沒有活動事務,則按照PROPAGATION_REQUIRED屬性執行(開啟一個新的事務) 

(1) PROPAGATION_REQUIRED: 如果存在一個事務,則支援當前事務。如果沒有事務則開啟一個新的事務

//事務屬性 PROPAGATION_REQUIRED
methodA{
    ……
    methodB();
    ……
}
//事務屬性 PROPAGATION_REQUIRED
methodB{
   ……
}

單獨呼叫methodB方法,相當於

Main{ 
    Connection con=null; 
    try{ 
        con = getConnection(); 
        con.setAutoCommit(false); 

        //方法呼叫
        methodB(); 

        //提交事務
        con.commit(); 
    } Catch(RuntimeException ex) { 
        //回滾事務
        con.rollback();   
    } finally { 
        //釋放資源
        closeCon(); 
    } 
} 

Spring保證在methodB方法中所有的呼叫都獲得到一個相同的連線。在呼叫methodB時,沒有一個存在的事務,所以獲得一個新

的連線,開啟了一個新的事務。 

單獨呼叫MethodA時,在MethodA內又會呼叫MethodB.

main{ 
    Connection con = null; 
    try{ 
        con = getConnection(); 
        methodA(); 
        con.commit(); 
    } catch(RuntimeException ex) { 
        con.rollback(); 
    } finally {    
        closeCon(); 
    }  
} 

呼叫方法A時候,沒有事務就開啟一個事務,MethodA呼叫MethodB,因為已經有事務,MethodB就加入這個事務

(2) PROPAGATION_SUPPORTS:  如果存在一個事務,支援當前事務。如果沒有事務,則非事務的執行。但是對於事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少許不同。

//事務屬性 PROPAGATION_REQUIRED
methodA(){
  methodB();
}

//事務屬性 PROPAGATION_SUPPORTS
methodB(){
  ……
}

單獨呼叫MethodB是非事務的執行,呼叫MethodA時候加入事務,MethodB支援事務執行

(3) PROPAGATION_MANDATORY 如果已經存在一個事務,支援當前事務。如果沒有一個活動的事務,則丟擲異常

//事務屬性 PROPAGATION_REQUIRED
methodA(){
    methodB();
}

//事務屬性 PROPAGATION_MANDATORY
    methodB(){
    ……
}

當單獨呼叫methodB時,因為當前沒有一個活動的事務,則會丟擲異常throw new IllegalTransactionStateException(“Transaction

propagation ‘mandatory’ but no existing transaction found”);當呼叫methodA時,methodB則加入到methodA的事務中,事務地

執行。

(4) PROPAGATION_REQUIRES_NEW 總是開啟一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起

//事務屬性 PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//事務屬性 PROPAGATION_REQUIRES_NEW
methodB(){
    ……
}

呼叫方法A相當於

main(){
    TransactionManager tm = null;
    try{
        //獲得一個JTA事務管理器
        tm = getTransactionManager();
        tm.begin();//開啟一個新的事務
        Transaction ts1 = tm.getTransaction();
        doSomeThing();
        tm.suspend();//掛起當前事務
        try{
            tm.begin();//重新開啟第二個事務
            Transaction ts2 = tm.getTransaction();
            methodB();
            ts2.commit();//提交第二個事務
        } Catch(RunTimeException ex) {
            ts2.rollback();//回滾第二個事務
        } finally {
            //釋放資源
        }
        //methodB執行完後,恢復第一個事務
        tm.resume(ts1);
        doSomeThingB();
        ts1.commit();//提交第一個事務
    } catch(RunTimeException ex) {
        ts1.rollback();//回滾第一個事務
    } finally {
        //釋放資源
    }
}

在這裡,我把ts1稱為外層事務,ts2稱為內層事務。從上面的程式碼可以看出,ts2與ts1是兩個獨立的事務,互不相干。Ts2是否成

功並不依賴於 ts1。如果methodA方法在呼叫methodB方法後的doSomeThingB方法失敗了,而methodB方法所做的結果依然被

提交。而除了 methodB之外的其它程式碼導致的結果卻被回滾了。使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager作為事務管理器。

(5)PROPAGATION_NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務。使用PROPAGATION_NOT_SUPPOR

TED,也需要使用JtaTransactionManager作為事務管理器。(程式碼示例同上,可同理推出)

(6)PROPAGATION_NEVER 總是非事務地執行,如果存在一個活動事務,則丟擲異常。

(7)PROPAGATION_NESTED如果一個活動的事務存在,則執行在一個巢狀的事務中. 如果沒有活動事務, 則按TransactionDefi

nition.PROPAGATION_REQUIRED 屬性執行。這是一個巢狀事務,使用JDBC 3.0驅動時,僅僅支援DataSourceTransactionManag

er作為事務管理器。需要JDBC 驅動的java.sql.Savepoint類。有一些J他的事務管理器實現可能也提供了同樣的功能。使用PROP

AGATION_NESTED,還需要把PlatformTransactionManager的nestedTransactionAllowed屬性設為true;而 nestedTransactionAllowed屬性值預設為false。

//事務屬性 PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//事務屬性 PROPAGATION_NESTED
methodB(){
    ……
}

單獨呼叫methodB相當於事務屬性為PROPAGATION_NESTED,開啟一個新的事務,如果呼叫methodA相當於

main(){
    Connection con = null;
    Savepoint savepoint = null;
    try{
        con = getConnection();
        con.setAutoCommit(false);
        doSomeThingA();
        savepoint = con2.setSavepoint();
        try{
            methodB();
        } catch(RuntimeException ex) {
            con.rollback(savepoint);
        } finally {
            //釋放資源
        }
        doSomeThingB();
        con.commit();
    } catch(RuntimeException ex) {
        con.rollback();
    } finally {
        //釋放資源
    }
}

methodB方法呼叫前,將當前的狀態儲存到savepoint,如果methodB執行失敗,回到savapoint,methodB後面的方法繼續執行,但是如果doSomeThingB執行失敗,包括methodB方法都將回滾。

巢狀事務,分為內層事務和外層事務,內層事務的失敗不會影響外層事務,但是外層事務的失敗,內層事務將回滾

支援:DataSourceTransactionManager使用savepoint支援PROPAGATION_NESTED時,需要JDBC 3.0以上驅動及1.4以上的JDK版本支援。其它的JTA TrasactionManager實現可能有不同的支援方式。

PROPAGATION_NESTED 與PROPAGATION_REQUIRES_NEW比較

相同點:如果沒有事務,都回去開啟一個新的事務

不同點: PROPAGATION_REQUIRES_NEW 外層事務和內層事務是兩個獨立的事務,外層的失敗不會引起內層事務的回滾。

內層事務的失敗,也不會引起外層的回滾。是獨立的兩個事務。而PROPAGATION_NESTED 內層事務相對是外層事務的一個分支,記憶體事務的失敗,不會影響到外層事務的失敗,但是外層事務的事務失敗,內層事務也失敗。內層事務的等外層事務提交。

參考: https://blog.csdn.net/trigl/article/details/50968079

相關文章