Spring事務級別以及嵌套事務

Spring 的事務級別:

引用

PROPAGATION_REQUIRED -- 支持當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。 
PROPAGATION_SUPPORTS -- 支持當前事務,如果當前沒有事務,就以非事務方式執行。 
PROPAGATION_MANDATORY -- 支持當前事務,如果當前沒有事務,就拋出異常。 
PROPAGATION_REQUIRES_NEW -- 新建事務,如果當前存在事務,把當前事務掛起。 
PROPAGATION_NOT_SUPPORTED -- 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。 
PROPAGATION_NEVER -- 以非事務方式執行,如果當前存在事務,則拋出異常。 
PROPAGATION_NESTED -- 如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則進行與PROPAGATION_REQUIRED類似的操作。 
前六個策略類似於EJB CMT,第七個(PROPAGATION_NESTED)是Spring所提供的一個特殊變量。 
它要求事務管理器或者使用JDBC 3.0 Savepoint API提供嵌套事務行爲(如Spring的DataSourceTransactionManager) 

其作者的文章片段:

引用

假如有兩個業務接口 ServiceA 和 ServiceB, 其中 ServiceA 中有一個方法實現如下 

/** 
* 事務屬性配置爲 PROPAGATION_REQUIRED 
*/ 
void methodA() { 
// 調用 ServiceB 的方法 
ServiceB.methodB(); 


那麼如果 ServiceB 的 methodB  如果配置了事務, 就必須配置爲 PROPAGATION_NESTED 
學習了。


這種想法可能害了不少人, 認爲 Service 之間應該避免互相調用, 其實根本不用擔心這點,PROPAGATION_REQUIRED 已經說得很明白, 
如果當前線程中已經存在事務, 方法調用會加入此事務, 果當前沒有事務,就新建一個事務, 所以 ServiceB#methodB() 的事務只要遵循最普通的規則配置爲 PROPAGATION_REQUIRED 即可, 如果 ServiceB#methodB (我們稱之爲內部事務, 爲下文打下基礎) 拋了異常, 那麼 ServiceA#methodA(我們稱之爲外部事務) 如果沒有特殊配置此異常時事務提交 (即 +MyCheckedException的用法), 那麼整個事務是一定要 rollback 的, 什麼 Service 只能調 Dao 之類的言論純屬無稽之談, spring 只負責配置了事務屬性方法的攔截, 它怎麼知道你這個方法是在 Service 還是 Dao 裏 ? 


ROPAGATION_REQUIRES_NEW 啓動一個新的, 不依賴於環境的 "內部" 事務. 這個事務將被完全 commited 或 rolled back 而不依賴於外部事務, 它擁有自己的隔離範圍, 自己的鎖, 等等. 當內部事務開始執行時, 外部事務將被掛起, 內務事務結束時, 外部事務將繼續執行. 


    另一方面, PROPAGATION_NESTED 開始一個 "嵌套的" 事務,  它是已經存在事務的一個真正的子事務. 潛套事務開始執行時,  它將取得一個 savepoint. 如果這個嵌套事務失敗, 我們將回滾到此 savepoint. 潛套事務是外部事務的一部分, 只有外部事務結束後它纔會被提交. 

    由此可見, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區別在於, PROPAGATION_REQUIRES_NEW 完全是一個新的事務, 而 PROPAGATION_NESTED 則是外部事務的子事務, 如果外部事務 commit, 潛套事務也會被 commit, 這個規則同樣適用於 roll back. 

[java] view plain copy
  1. ServiceA {    
  2.         
  3.     /**  
  4.      * 事務屬性配置爲 PROPAGATION_REQUIRED  
  5.      */    
  6.     void methodA() {    
  7.         ServiceB.methodB();    
  8.     }    
  9.     
  10. }    
  11.     
  12. ServiceB {    
  13.         
  14.     /**  
  15.      * 事務屬性配置爲 PROPAGATION_REQUIRES_NEW  
  16.      */     
  17.     void methodB() {    
  18.     }    
  19.         
  20. }   

這種情況下, 因爲 ServiceB#methodB 的事務屬性爲 PROPAGATION_REQUIRES_NEW, 所以兩者不會發生任何關係, ServiceA#methodA 和 ServiceB#methodB 不會因爲對方的執行情況而影響事務的結果, 因爲它們根本就是兩個事務, 在 ServiceB#methodB 執行時 ServiceA#methodA 的事務已經掛起了 (關於事務掛起的內容已經超出了本文的討論範圍, 有時間我會再寫一些掛起的文章) . 


[java] view plain copy
  1. ServiceA {    
  2.         
  3.     /**  
  4.      * 事務屬性配置爲 PROPAGATION_REQUIRED  
  5.      */    
  6.     void methodA() {    
  7.         ServiceB.methodB();    
  8.     }    
  9.     
  10. }    
  11.     
  12. ServiceB {    
  13.         
  14.     /**  
  15.      * 事務屬性配置爲 PROPAGATION_NESTED  
  16.      */     
  17.     void methodB() {    
  18.     }    
  19.         
  20. }       

現在的情況就變得比較複雜了, ServiceB#methodB 的事務屬性被配置爲 PROPAGATION_NESTED, 此時兩者之間又將如何協作呢? 從 Juergen Hoeller 的原話中我們可以找到答案, ServiceB#methodB 如果 rollback, 那麼內部事務(即 ServiceB#methodB) 將回滾到它執行前的 SavePoint(注意, 這是本文中第一次提到它, 潛套事務中最核心的概念), 而外部事務(即 ServiceA#methodA) 可以有以下兩種處理方式: 

1. 改寫 ServiceA 如下 

Java代碼  收藏代碼
  1. ServiceA {  
  2.       
  3.     /** 
  4.      * 事務屬性配置爲 PROPAGATION_REQUIRED 
  5.      */  
  6.     void methodA() {  
  7.         try {  
  8.             ServiceB.methodB();  
  9.         } catch (SomeException) {  
  10.             // 執行其他業務, 如 ServiceC.methodC();  
  11.         }  
  12.     }  
  13.   
  14. }  


這種方式也是潛套事務最有價值的地方, 它起到了分支執行的效果, 如果 ServiceB.methodB 失敗, 那麼執行 ServiceC.methodC(), 而 ServiceB.methodB 已經回滾到它執行之前的 SavePoint, 所以不會產生髒數據(相當於此方法從未執行過), 這種特性可以用在某些特殊的業務中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都沒有辦法做到這一點. (題外話 : 看到這種代碼, 似乎似曾相識, 想起了 prototype.js 中的 Try 函數 ) 

2. 代碼不做任何修改, 那麼如果內部事務(即 ServiceB#methodB) rollback, 那麼首先 ServiceB.methodB 回滾到它執行之前的 SavePoint(在任何情況下都會如此), 
   外部事務(即 ServiceA#methodA) 將根據具體的配置決定自己是 commit 還是 rollback (+MyCheckedException). 


面大致講述了潛套事務的使用場景, 下面我們來看如何在 spring 中使用 PROPAGATION_NESTED, 首先來看 AbstractPlatformTransactionManager 

Java代碼  收藏代碼
  1. /** 
  2.  * Create a TransactionStatus for an existing transaction. 
  3.  */  
  4. private TransactionStatus handleExistingTransaction(  
  5.         TransactionDefinition definition, Object transaction, boolean debugEnabled)  
  6.         throws TransactionException {  
  7.   
  8.    ... 省略  
  9.   
  10.     if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {  
  11.         if (!isNestedTransactionAllowed()) {  
  12.             throw new NestedTransactionNotSupportedException(  
  13.                     "Transaction manager does not allow nested transactions by default - " +  
  14.                     "specify 'nestedTransactionAllowed' property with value 'true'");  
  15.         }  
  16.         if (debugEnabled) {  
  17.             logger.debug("Creating nested transaction with name [" + definition.getName() + "]");  
  18.         }  
  19.         if (useSavepointForNestedTransaction()) {  
  20.             // Create savepoint within existing Spring-managed transaction,  
  21.             // through the SavepointManager API implemented by TransactionStatus.  
  22.             // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.  
  23.             DefaultTransactionStatus status =  
  24.                     newTransactionStatus(definition, transaction, falsefalse, debugEnabled, null);  
  25.             status.createAndHoldSavepoint();  
  26.             return status;  
  27.         }  
  28.         else {  
  29.             // Nested transaction through nested begin and commit/rollback calls.  
  30.             // Usually only for JTA: Spring synchronization might get activated here  
  31.             // in case of a pre-existing JTA transaction.  
  32.             doBegin(transaction, definition);  
  33.             boolean newSynchronization = (this.transactionSynchronization != SYNCHRONIZATION_NEVER);  
  34.             return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);  
  35.         }  
  36.     }  
  37. }  

   
一目瞭然 

1. 我們要設置 transactionManager 的 nestedTransactionAllowed 屬性爲 true, 注意, 此屬性默認爲 false!!! 

再看 AbstractTransactionStatus#createAndHoldSavepoint() 方法 

Java代碼  收藏代碼
  1. /** 
  2.  * Create a savepoint and hold it for the transaction. 
  3.  * @throws org.springframework.transaction.NestedTransactionNotSupportedException 
  4.  * if the underlying transaction does not support savepoints 
  5.  */  
  6. public void createAndHoldSavepoint() throws TransactionException {  
  7.     setSavepoint(getSavepointManager().createSavepoint());  
  8. }  


  可以看到 Savepoint 是 SavepointManager.createSavepoint 實現的, 再看 SavepointManager 的層次結構, 發現 
  其 Template 實現是 JdbcTransactionObjectSupport, 常用的 DatasourceTransactionManager, HibernateTransactionManager
  中的 TransactonObject 都是它的子類 : 

 
   
  JdbcTransactionObjectSupport 告訴我們必須要滿足兩個條件才能 createSavepoint : 
  
2. java.sql.Savepoint 必須存在, 即 jdk 版本要 1.4+ 
3. Connection.getMetaData().supportsSavepoints() 必須爲 true, 即 jdbc drive 必須支持 JDBC 3.0 


確保以上條件都滿足後, 你就可以嘗試使用 PROPAGATION_NESTED 了

個人理解:PROPAGATION_NESTED 和PROPAGATION_REQUIRES_NEW 的主要區別在於,如果 ServiceB.methodB 完成執行並“提交”後,如果後面的ServiceA.methodA部分失敗,那麼如果設置成PROPAGATION_NESTED 那麼ServiceB.methodB將需要回滾;如果設置成PROPAGATION_REQUIRES_NEW 那麼ServiceB.methodB將不受影響而持久化事務內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章