轉載地址1html
Spring 事務一個被訛傳很廣說法是:一個事務方法不該該調用另外一個事務方法,不然將產生兩個事務。結果形成開發人員在設計事務方法時束手束腳,生怕一不當心就踩到地雷。spring
其實這種是不認識 Spring 事務傳播機制而形成的誤解,Spring 對事務控制的支持統一在 TransactionDefinition 類中描述,該類有如下幾個重要的接口方法:數據庫
很明顯,除了事務的傳播行爲外,事務的其它特性 Spring 是藉助底層資源的功能來完成的,Spring 無非只充當個代理的角色。可是事務的傳播行爲倒是 Spring 憑藉自身的框架提供的功能,是 Spring 提供給開發者最珍貴的禮物,訛傳的說法玷污了 Spring 事務框架最美麗的光環。小程序
所謂事務傳播行爲就是多個事務方法相互調用時,事務如何在這些方法間傳播。Spring 支持 7 種事務傳播行爲:session
Spring 默認的事務傳播行爲是 PROPAGATION_REQUIRED,它適合於絕大多數的狀況。假設 ServiveX#methodX() 都工做在事務環境下(即都被 Spring 事務加強了),假設程序中存在以下的調用鏈:Service1#method1()->Service2#method2()->Service3#method3(),那麼這 3 個服務類的 3 個方法經過 Spring 的事務傳播機制都工做在同一個事務中。框架
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------函數
轉載地址2ui
咱們都知道事務的概念,那麼事務的傳播特性是什麼呢?(此處着重介紹傳播特性的概念,關於傳播特性的相關配置就不介紹了,能夠查看spring的官方文檔)
在咱們用SSH開發項目的時候,咱們通常都是將事務設置在Service層那麼當咱們調用Service層的一個方法的時候它可以保證咱們的這個方法中執行的全部的對數據庫的更新操做保持在一個事務中,在事務層裏面調用的這些方法要麼所有成功,要麼所有失敗。那麼事務的傳播特性也是從這裏提及的。
若是你在你的Service層的這個方法中,除了調用了Dao層的方法以外,還調用了本類的其餘的Service方法,那麼在調用其餘的Service方法的時候,這個事務是怎麼規定的呢,我必須保證我在我方法裏掉用的這個方法與我自己的方法處在同一個事務中,不然若是保證事物的一致性。事務的傳播特性就是解決這個問題的,「事務是會傳播的」在Spring中有針對傳播特性的多種配置咱們大多數狀況下只用其中的一種:PROPGATION_REQUIRED:這個配置項的意思是說當我調用service層的方法的時候開啓一個事務(具體調用那一層的方法開始建立事務,要看你的aop的配置),那麼在調用這個service層裏面的其餘的方法的時候,若是當前方法產生了事務就用當前方法產生的事務,不然就建立一個新的事務。這個工做使由Spring來幫助咱們完成的。
之前沒有Spring幫助咱們完成事務的時候咱們必須本身手動的控制事務,例如當咱們項目中僅僅使用hibernate,而沒有集成進spring的時候,咱們在一個service層中調用其餘的業務邏輯方法,爲了保證事物必須也要把當前的hibernatesession傳遞到下一個方法中,或者採用ThreadLocal的方法,將session傳遞給下一個方法,其實都是一個目的。如今這個工做由spring來幫助咱們完成,就可讓咱們更加的專一於咱們的業務邏輯。而不用去關心事務的問題。
默認狀況下當發生RuntimeException的狀況下,事務纔會回滾,因此要注意一下若是你在程序發生錯誤的狀況下,有本身的異常處理機制定義本身的Exception,必須從RuntimeException類繼承這樣事務纔會回滾!.net
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------hibernate
最近遇到了一個spring事務致使的問題,因此寫了幾個小程序瞭解了一下事務的傳播特性,下面分別舉例子分別看看事務的傳播特性。
事務的幾種傳播特性
1. PROPAGATION_REQUIRED: 若是存在一個事務,則支持當前事務。若是沒有事務則開啓
/** * TransactionTestService test1和test2配有事務(PROPAGATION_REQUIRED) */ public interface TransactionTestService { //事務屬性 PROPAGATION_REQUIRED public void test1() throws Exception{ avInfoTaskTunnel.insertAvInfoTask(); test2(); } //事務屬性 PROPAGATION_REQUIRED public void test2() throws Exception{ avRequestTunnel.insertAvRequest(); throw new Exception(); } } /** * main */ public class TransactionTestMain { public static void main(String[] args) throws Exception{ TransactionTestService transactionTestService = (TransactionTestService)context.getBean("transactionTestService"); try { transactionTestService.test1(); } catch (Exception e) { } } }
上述代碼中test1()和test2()都配有PROPAGATION_REQUIRED事務屬性,test1()內部調用test2(),這樣test1()和test2()方法將都處於同一事務之中,當在test2()中拋出異常,會致使test1()和test2()方法中的事務都回滾。
可是,若是test1()方法對調用test2()時捕獲異常,結果會是怎樣的呢? test1應該能正常寫入沒問題,那麼test2呢?
//test1()中捕獲test2()拋出的異常 public void test1() throws Exception{ avInfoTaskTunnel.insertAvInfoTask(); try { test2(); }catch (Exception e) { } }
最後的結果test2()也將會正常的寫入。其實,上面狀況中若是隻是test1()配有PROPAGATION_REQUIRED事務屬性,test2()不配置任何事務屬性,發生的結果也是一致的。上面的情形至關於:
public static void main(String[] args) throws Exception{ Connection con = null; try { con=getConnection(); con.setAutoCommit(false); transactionTestService.test1(); //test1()和test2()處於同一事務之中 con.commit(); } catch (Exception e) { con.rollback(); } finally { closeCon(); } }
上述test1()和test2()是同一個類的兩個方法,那麼要是它們處於不一樣類呢?
//main函數不變,test1()中調用test2()地方換成調用另外一個類中配有PROPAGATION_REQUIRED事務屬性的方法 //不對otherService.test2()捕獲異常的情形就不必說了,一定都回滾。 public void test1() throws Exception{ avInfoTaskTunnel.insertAvInfoTask(); try { otherService.test2(); //PROPAGATION_REQUIRED事務屬性 } catch (Exception e) { } } //otherService.test2() public void test2() throws Exception{ avRequestTunnel.insertAvRequest(); throw new Exception(); }
上述一樣捕獲了異常,可是結果會怎樣呢? 結果有點出乎意料,與以前test1(),test2()處於同一類的情形不一樣,這個時候,兩個方法都將回滾,而且在調用test1()的地方會拋出下面異常。這是因爲子事務在回滾的時候已經將主事務標記成了rollback-only,這樣致使主事務在提交的時候就會拋出下面這個異常。 另外:若是otherService.test2()沒有配置任何事務屬性,那麼test2()拋出異常的時候,將致使test1()和test2()都回滾。
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
上述狀況網上查詢到一種解決方式是在transactionManager中將globalRollbackOnParticipationFailure 設置爲false(默認是true)。可是這樣又帶來另外一個問題,子事務也給一併提交了(這個時候子事務產生異常,不想提交),具體的解決方式還沒找到,可是我以爲不該該將這兩個配置在同一事務中。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"> <ref local="dataSource" /> </property> <property name="globalRollbackOnParticipationFailure" value="false" /> </bean>
2. PROPAGATION_SUPPORTS: 若是存在一個事務,支持當前事務。若是沒有事務,則非事務的執行
//TransactionTestService test1()配有事務(PROPAGATION_SUPPORTS) public void test1() throws Exception{ avInfoTaskTunnel.insertAvInfoTask(); throw new Exception(); } /** * main */ public class TransactionTestMain { public static void main(String[] args) throws Exception{ TransactionTestService transactionTestService = (TransactionTestService)context.getBean("transactionTestService"); try { transactionTestService.test1(); } catch (Exception e) { } } }
TransactionTestService的test1()配有PROPAGATION_SUPPORTS事務屬性,咱們知道這種情形下若是配的是PROPAGATION_REQUIRED事務屬性,那麼test1()將新建事務運行,可是PROPAGATION_SUPPORTS屬性不會新建,這種情形下,test1()方法將正常提交。那若是是外層事務配有事務呢?以下所示,此種情形test2()將處於事務之中了。
//PROPAGATION_REQUIRED事務屬性 public void test1() throws Exception{ avInfoTaskTunnel.insertAvInfoTask(); test2(); } //PROPAGATION_SUPPORTS事務屬性 public void test2() throws Exception{ avRequestTunnel.insertAvRequest(); throw new Exception(); }
3. PROPAGATION_MANDATORY: 若是已經存在一個事務,支持當前事務。若是沒有一個活動的事務,則拋出異常。
//情形1:PROPAGATION_REQUIRED事務屬性 情形2:不配任何事務屬性 public void test1() throws Exception{ avInfoTaskTunnel.insertAvInfoTask(); otherService.test2(); } //otherService的test2()配置PROPAGATION_MANDATORY事務屬性 public void test2() throws Exception { avRequestTunnel.insertAvRequest(); throw new Exception(); } //test1()處於情形2時拋出異常,test2()不可以提交,可是test1()倒是能夠成功提交的 org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
上述狀況,當test1()處於情形1時,會是的test1()和test2()都處於事務當中;test1()處於情形2時,就會拋異常,可是這個時候test2()不可以提交,可是test1()倒是能夠成功提交的。此外,還有一點,注意上述test2()是處於另外一個類中的,若是是處於同一個類,那麼PROPAGATION_MANDATORY不會由於外層是否有事務而拋異常。
4. PROPAGATION_REQUIRES_NEW: 老是開啓一個新的事務。若是一個事務已經存在,則將這個存在的事務掛起。
//情形1:PROPAGATION_REQUIRED事務屬性 情形2:不配任何事務屬性 public void test1() throws Exception{ avInfoTaskTunnel.insertAvInfoTask(); otherService.test2(); } //otherService的test2()配置PROPAGATION_REQUIRES_NEW事務屬性 public void test2() throws Exception { avRequestTunnel.insertAvRequest(); throw new Exception(); }
上述狀況,test1()處於情形2時test2()新建事務,這點沒有問題。那若是test1()處於情形1呢?也就是說test1()已經有事務了。結果是test2()處於新的事務中,怎麼肯定是處於新的事務中呢?看下面代碼:
//test1配置PROPAGATION_REQUIRED事務屬性 public void test1() throws Exception{ avInfoTaskTunnel.insertAvInfoTask(); otherService.test2(); //PROPAGATION_REQUIRES_NEW事務屬性 } //otherService.test2() public void test2() throws Exception{ avRequestTunnel.insertAvRequest(); throw new Exception(); }
若是是在同一事務中,那麼情形將同於講述PROPAGATION_REQUIRED屬性時的情形,test1()和test2()都將回滾,而且拋出異常事務被打上rollback-only標記的異常。可是這裏,結果就是test2()回滾,test1正常提交,因此otherService.test2()處於新的事務中。注意:上述狀況test2()也是另外一個類的方法,若是屬於同一類,也就是test1()和test2()處於同一個類,test1()中調用test2(),那麼配置的PROPAGATION_REQUIRES_NEW將無效(跟test2()什麼事務屬性都沒配置同樣)。可是若是是main函數中直接調用test2(),那麼仍是會起一個新的事務。
另一種證實test1()和test2()處於不一樣事務的方式是,在test2()不拋出異常,而後再test1()調用了test2()以後,拋出異常,最後結果是()回滾,test2()正常提交。
5. PROPAGATION_NOT_SUPPORTED: 老是非事務地執行,並掛起任何存在的事務。
//test1配置PROPAGATION_REQUIRED事務屬性 public void test1() throws Exception{ avInfoTaskTunnel.insertAvInfoTask(); otherService.test2(); //PROPAGATION_NOT_SUPPORTED事務屬性 } //otherService.test2() public void test2() throws Exception{ avRequestTunnel.insertAvRequest(); throw new Exception(); }
若是otherService.test2() 沒有配置事務屬性,那麼test2()拋出異常時,test1()和test2()都回滾。可是如今test2()配置了PROPAGATION_NOT_SUPPORTED事務屬性,那麼test2()將以非事務運行,而test1()繼續運行在事務中,也就是說,此時,test1()回滾,test2()正常提交。注意:若是test1()和test2()在同一類中,那麼test2()的PROPAGATION_NOT_SUPPORTED失效。
6. PROPAGATION_NEVER: 老是非事務地執行,若是存在一個活動事務,則拋出異常
//test1配置PROPAGATION_REQUIRED事務屬性 public void test1() throws Exception{ avInfoTaskTunnel.insertAvInfoTask(); otherService.test2(); //PROPAGATION_NEVER事務屬性 } //otherService.test2() public void test2() throws Exception{ avRequestTunnel.insertAvRequest(); throw new Exception(); } //test1()配置PROPAGATION_REQUIRED事務屬性, otherService.test2()配置PROPAGATION_NEVER事務屬性,將拋下面異常: org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
固然上述狀況,若是是test1()和test2()在同一類中,那麼PROPAGATION_NEVER也將失效。
7. PROPAGATION_NESTED:若是一個活動的事務存在,則運行在一個嵌套的事務中. 若是沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行
最容易弄混淆的實際上是 PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED。PROPAGATION_REQUIRES_NEW 啓動一個新的, 不依賴於環境的 "內部" 事務. 這個事務將被徹底 commited 或 rolled back 而不依賴於外部事務, 它擁有本身的隔離範圍, 本身的鎖, 等等. 當內部事務開始執行時, 外部事務將被掛起, 內務事務結束時, 外部事務將繼續執行。另外一方面, PROPAGATION_NESTED 開始一個 "嵌套的" 事務, 它是已經存在事務的一個真正的子事務. 嵌套事務開始執行時, 它將取得一個 savepoint. 若是這個嵌套事務失敗, 咱們將回滾到此 savepoint.。嵌套事務是外部事務的一部分, 只有外部事務結束後它纔會被提交。因而可知, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區別在於, PROPAGATION_REQUIRES_NEW 徹底是一個新的事務, 而 PROPAGATION_NESTED 則是外部事務的子事務, 若是外部事務 commit, 潛套事務也會被 commit, 這個規則一樣適用於 roll back.
//test1配置PROPAGATION_REQUIRED事務屬性 public void test1() throws Exception{ avInfoTaskTunnel.insertAvInfoTask(); otherService.test2(); //PROPAGATION_NESTED事務屬性 } //otherService.test2() public void test2() throws Exception{ avRequestTunnel.insertAvRequest(); }
上述狀況,若是otherService.test2()配置PROPAGATION_REQUIRES_NEW事務屬性,這樣test1()回滾,可是test2()正常提交,由於這是兩個事務。可是若是otherService.test2()配置PROPAGATION_NESTED事務屬性,那麼test1()和test2()都將回滾。