1. 程式人生 > >事務的巢狀概念

事務的巢狀概念

所謂事務的巢狀就是兩個事務方法之間相互呼叫。spring事務開啟 ,或者是基於介面的或者是基於類的代理被建立(注意一定要是代理,不能手動new 一個物件,並且此類(有無介面都行)一定要被代理——spring中的bean只要納入了IOC管理都是被代理的)。所以在同一個類中一個方法呼叫另一個方法有事務的方法,事務是不會起作用的。

###

Spring預設情況下會對執行期例外(RunTimeException),即uncheck異常,進行事務回滾。

如果遇到checked異常就不回滾。 如何改變預設規則: 1 讓checked例外也回滾:在整個方法前加上 @Transactional(rollbackFor=Exception.class) 2 讓unchecked例外不回滾: @Transactional(notRollbackFor=RunTimeException.class) 3 不需要事務管理的(只查詢的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)

上面三種方式也可在xml配置

spring事務傳播屬性

 在 spring的 TransactionDefinition介面中一共定義了六種事務傳播屬性:

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) 

舉例淺析Spring巢狀事務

ServiceA#methodA(我們稱之為外部事務),ServiceB#methodB(我們稱之為外部事務)

  1. ServiceA {

  2. void methodA() {

  3. ServiceB.methodB();

  4. }

  5. }

  6. ServiceB {

  7. void methodB() {

  8. }

  9. }

PROPAGATION_REQUIRED

假如當前正要執行的事務不在另外一個事務裡,那麼就起一個新的事務  比如說,ServiceB.methodB的事務級別定義為PROPAGATION_REQUIRED, 那麼由於執行ServiceA.methodA的時候

  1、如果ServiceA.methodA已經起了事務,這時呼叫ServiceB.methodB,ServiceB.methodB看到自己已經執行在ServiceA.methodA的事務內部,就不再起新的事務。這時只有外部事務並且他們是共用的,所以這時ServiceA.methodA或者ServiceB.methodB無論哪個發生異常methodA和methodB作為一個整體都將一起回滾。

  2、如果ServiceA.methodA沒有事務,ServiceB.methodB就會為自己分配一個事務。這樣,在ServiceA.methodA中是沒有事務控制的。只是在ServiceB.methodB內的任何地方出現異常,ServiceB.methodB將會被回滾,不會引起ServiceA.methodA的回滾

PROPAGATION_SUPPORTS

如果當前在事務中,即以事務的形式執行,如果當前不再一個事務中,那麼就以非事務的形式執行 

PROPAGATION_MANDATORY

必須在一個事務中執行。也就是說,他只能被一個父事務呼叫。否則,他就要丟擲異常

PROPAGATION_REQUIRES_NEW

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

 比如我們設計ServiceA.methodA的事務級別為PROPAGATION_REQUIRED,ServiceB.methodB的事務級別為PROPAGATION_REQUIRES_NEW,那麼當執行到ServiceB.methodB的時候,ServiceA.methodA所在的事務就會掛起,ServiceB.methodB會起一個新的事務,等待ServiceB.methodB的事務完成以後,他才繼續執行。他與PROPAGATION_REQUIRED 的事務區別在於事務的回滾程度了。因為ServiceB.methodB是新起一個事務,那麼就是存在兩個不同的事務。

1、如果ServiceB.methodB已經提交,那麼ServiceA.methodA失敗回滾,ServiceB.methodB是不會回滾的。

2、如果ServiceB.methodB失敗回滾,如果他丟擲的異常被ServiceA.methodA的try..catch捕獲並處理,ServiceA.methodA事務仍然可能提交;如果他丟擲的異常未被ServiceA.methodA捕獲處理,ServiceA.methodA事務將回滾。

使用場景:

不管業務邏輯的service是否有異常,Log Service都應該能夠記錄成功,所以Log Service的傳播屬性可以配為此屬性。最下面將會貼出配置程式碼。

PROPAGATION_NOT_SUPPORTED

當前不支援事務。比如ServiceA.methodA的事務級別是PROPAGATION_REQUIRED ,而ServiceB.methodB的事務級別是PROPAGATION_NOT_SUPPORTED ,那麼當執行到ServiceB.methodB時,ServiceA.methodA的事務掛起,而他以非事務的狀態執行完,再繼續ServiceA.methodA的事務。

PROPAGATION_NEVER

不能在事務中執行。假設ServiceA.methodA的事務級別是PROPAGATION_REQUIRED, 而ServiceB.methodB的事務級別是PROPAGATION_NEVER ,那麼ServiceB.methodB就要丟擲異常了。 

PROPAGATION_NESTED

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

比如我們設計ServiceA.methodA的事務級別為PROPAGATION_REQUIRED,ServiceB.methodB的事務級別為PROPAGATION_NESTED,那麼當執行到ServiceB.methodB的時候,ServiceA.methodA所在的事務就會掛起,ServiceB.methodB會起一個新的子事務並設定savepoint,等待ServiceB.methodB的事務完成以後,他才繼續執行。。因為ServiceB.methodB是外部事務的子事務,那麼

1、如果ServiceB.methodB已經提交,那麼ServiceA.methodA失敗回滾,ServiceB.methodB也將回滾。

2、如果ServiceB.methodB失敗回滾,如果他丟擲的異常被ServiceA.methodA的try..catch捕獲並處理,ServiceA.methodA事務仍然可能提交;如果他丟擲的異常未被ServiceA.methodA捕獲處理,ServiceA.methodA事務將回滾。

理解Nested的關鍵是savepoint。他與PROPAGATION_REQUIRES_NEW的區別是:

PROPAGATION_REQUIRES_NEW 完全是一個新的事務,它與外部事務相互獨立; 而 PROPAGATION_NESTED 則是外部事務的子事務, 如果外部事務 commit, 巢狀事務也會被 commit, 這個規則同樣適用於 roll back. 

在 spring 中使用 PROPAGATION_NESTED的前提:

1. 我們要設定 transactionManager 的 nestedTransactionAllowed 屬性為 true, 注意, 此屬性預設為 false!!! 

2. java.sql.Savepoint 必須存在, 即 jdk 版本要 1.4+ 

3. Connection.getMetaData().supportsSavepoints() 必須為 true, 即 jdbc drive 必須支援 JDBC 3.0 

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

##############################################################################

Log Service配置事務傳播

不管業務邏輯的service是否有異常,Log Service都應該能夠記錄成功,通常有異常的呼叫更是使用者關心的。Log Service如果沿用業務邏輯Service的事務的話在丟擲異常時將沒有辦法記錄日誌(事實上是回滾了)。所以希望Log Service能夠有獨立的事務。日誌和普通的服務應該具有不同的策略。Spring 配置檔案transaction.xml:

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <beans xmlns="http://www.springframework.org/schema/beans"

  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"

  4. xmlns:tx="http://www.springframework.org/schema/tx"

  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

  6. <!-- configure transaction -->

  7. <tx:advice id="defaultTxAdvice" transaction-manager="transactionManager">

  8. <tx:attributes>

  9. <tx:method name="get*" read-only="true" />

  10. <tx:method name="query*" read-only="true" />

  11. <tx:method name="find*" read-only="true" />

  12. <tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.Exception" />

  13. </tx:attributes>

  14. </tx:advice>

  15. <tx:advice id="logTxAdvice" transaction-manager="transactionManager">

  16. <tx:attributes>

  17. <tx:method name="get*" read-only="true" />

  18. <tx:method name="query*" read-only="true" />

  19. <tx:method name="find*" read-only="true" />

  20. <tx:method name="*" propagation="REQUIRES_NEW"

  21. rollback-for="java.lang.Exception" />

  22. </tx:attributes>

  23. </tx:advice>

  24. <aop:config>

  25. <aop:pointcut id="defaultOperation"

  26. expression="@within(com.homent.util.DefaultTransaction)" />

  27. <aop:pointcut id="logServiceOperation"

  28. expression="execution(* com.homent.service.LogService.*(..))" />

  29. <aop:advisor advice-ref="defaultTxAdvice" pointcut-ref="defaultOperation" />

  30. <aop:advisor advice-ref="logTxAdvice" pointcut-ref="logServiceOperation" />

  31. </aop:config>

  32. </beans>

 如上面的Spring配置檔案所示,日誌服務的事務策略配置為propagation="REQUIRES_NEW",告訴Spring不管上下文是否有事務,Log Service被呼叫時都要求一個完全新的只屬於Log Service自己的事務。通過該事務策略,Log Service可以獨立的記錄日誌資訊,不再受到業務邏輯事務的干擾。