SPRing提供了編程式事務(wù)和聲明式事務(wù),但由于編程性事務(wù)的侵入性,開發(fā)中普遍會使用Spring的聲明式事務(wù),下文中所說的Spring事務(wù)也都是指聲明式事務(wù)。
Spring聲明式事務(wù)底層是建立在AOP的基礎(chǔ)上的,其本質(zhì)就是對方法前后進行攔截,然后在目標方法之前創(chuàng)建或加入一個事務(wù),在執(zhí)行完目標方法之后根據(jù)執(zhí)行執(zhí)行情況提交或回滾事務(wù)。
聲明式事務(wù)最大的優(yōu)點就是不需要在業(yè)務(wù)邏輯代碼中摻雜事務(wù)管理的代碼,Spring事務(wù)提供兩種事務(wù)管理方式:基于注解和基于xml配置,我們只需要簡單的配置即可使用。
相比于編程式事務(wù),由于聲明式事務(wù)是基于AOP實現(xiàn)的,所以只能實現(xiàn)對方法的粗粒度的控制,而無法做到代碼塊級別的細粒度控制。
Spring事務(wù)包括5中參數(shù),分別是:傳播行為,隔離級別,是否只讀,事務(wù)超時時間,回滾規(guī)則
本文分析下5種事務(wù)參數(shù),然后對具體的傳播行為給出Demo,以此給出其“細粒度”的事務(wù)實現(xiàn)。
傳播行為定義了關(guān)于客戶端和被調(diào)用方法的事務(wù)邊界,Spring中定義了7中傳播行為。
傳播行為 | 意義 |
---|---|
PROPAGATION_REQUIRED | 默認的Spring事務(wù)傳播規(guī)則;表示當前方法必須在一個事務(wù)中運行,如果一個現(xiàn)有事務(wù)正在運行中,該方法將在那個事務(wù)中運行,否則就會新建一個事務(wù) |
PROPAGATION_REQUIRES_NEW | 表示當前方法必須在它自己的事務(wù)里運行。每次都會新建一個事務(wù),如果當前有一個事務(wù)在運行的話,則將在這個方法運行期間掛起,待新事務(wù)執(zhí)行完成后,上下文事務(wù)再恢復(fù)執(zhí)行 |
PROPAGATION_SUPPORTS | 表示如果上下文中存在事務(wù),就在當前事務(wù)中執(zhí)行,否則使用非事務(wù)的方式執(zhí)行 |
PROPAGATION_NOT_SUPPORTED | 表示該方法不應(yīng)該在一個事務(wù)中運行。如果上下文中存在事務(wù),它將在該方法執(zhí)行期間被掛起 |
PROPAGATION_MANDATORY | 表示該方法必須在一個事務(wù)中運行,如果沒有事務(wù),將拋出異常 |
PROPAGATION_NEVER | 表示該方法不能在一個事務(wù)中運行,如果有事務(wù),則會拋出異常 |
PROPAGATION_NESTED | 表示如果當前正有一個事務(wù)在運行中,則該方法會運行在一個嵌套事務(wù)中。被嵌套的事務(wù)可以獨立于封裝事務(wù)進行提交或回滾。如果不存在事務(wù),則新建事務(wù)。 |
對嵌套事務(wù)的理解:
嵌套是子事務(wù)在父事務(wù)中執(zhí)行,子事務(wù)是父事務(wù)的一部分,在進入子事務(wù)之前,父事務(wù)建立一個回滾點,叫save point,然后執(zhí)行子事務(wù),這個子事務(wù)也算是父事務(wù)的一部分,然后子事務(wù)執(zhí)行結(jié)束,父事務(wù)繼續(xù)執(zhí)行。下面看幾個問題就懂了
如果子事務(wù)回滾,會發(fā)生什么?
答:父事務(wù)會回滾到save point,然后嘗試其他的事務(wù)或者其他的業(yè)務(wù)邏輯,父事務(wù)之前的操作不會受到影響,更不會自動回滾
如果父事務(wù)回滾,會發(fā)生什么?
答:父事務(wù)回滾,子事務(wù)也會跟著回滾。父事務(wù)結(jié)束之前,子事務(wù)不會提交。
事務(wù)的提交是什么情況?
答:子事務(wù)是父事務(wù)的一部分,所以子事務(wù)先提交,父事務(wù)再提交
隔離級別定義一個事務(wù)可能接受其他并發(fā)事務(wù)活動受影響的程度。也可以想象成事務(wù)對于數(shù)據(jù)處理的自私程度。
多個事務(wù)同時運行,經(jīng)常會為了完成他們的工作而操作同一個數(shù)據(jù)。并發(fā)雖然是必需的,但是會導致以下問題:
脹讀——A事務(wù)讀取數(shù)據(jù)并修改,未提交之間B事務(wù)又讀取了數(shù)據(jù)不可重復(fù)讀——A事務(wù)讀取數(shù)據(jù),B事務(wù)讀取數(shù)據(jù)并修改,A事務(wù)再讀取數(shù)據(jù)時發(fā)現(xiàn)兩次數(shù)據(jù)不一致幻讀——事務(wù)A讀取或刪除了全部數(shù)據(jù),事務(wù)B又insert一條數(shù)據(jù),這時事務(wù)A發(fā)現(xiàn)莫名其妙的多了一條數(shù)據(jù)理想狀態(tài)下,事務(wù)之間是完全隔離的。但是完全隔離會影響性能,因為隔離的實現(xiàn)依賴于數(shù)據(jù)庫中的鎖,侵占性鎖會阻礙并發(fā),要求事務(wù)互相等待
考慮到完全隔離會影響性能,而且并不是所有的情況都要求完全隔離,所以有時候可以在事務(wù)隔離方面靈活處理。因此,就有好幾個隔離級別。
隔離級別 | 含義 |
---|---|
ISOLATION_DEFAULT | 使用后端數(shù)據(jù)庫默認的隔離級別 |
ISOLATION_READ_UNCOMMITTED | 允許讀取未提交的更改。 可能導致臟讀、幻讀或不可重復(fù)讀。 |
ISOLATION_READ_COMMITTED | 允許從已經(jīng)提交的并發(fā)事務(wù)讀取。可防止臟讀,但幻讀和不可重復(fù)讀仍可能會發(fā)生。 |
ISOLATION_REPEATABLE_READ | 對相同字段的多次讀取的結(jié)果是一致的,除非數(shù)據(jù)被當前事務(wù)本身改變。可防止臟讀和不可重復(fù)讀,但幻影讀仍可能發(fā)生。 |
ISOLATION_SERIALIZABLE | 完全服從ACID的隔離級別,確保不發(fā)生臟讀、不可重復(fù)讀和幻影讀。這在所有隔離級別中也是最慢的,因為它通常是通過完全鎖定當前事務(wù)所涉及的數(shù)據(jù)表來完成的。 |
如果一個事務(wù)只對后端數(shù)據(jù)庫執(zhí)行讀操作,那么該數(shù)據(jù)庫就可能利用那個事務(wù)的只讀特性,采取某些優(yōu)化措施。
由于只讀的優(yōu)化措施是在一個事務(wù)啟動時由后端數(shù)據(jù)庫實施的,因此,只有對于那些具有可能啟動一個新事物的傳播行為(PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED)的方法來說,將事務(wù)聲明為只讀才有意義
假設(shè)事務(wù)的運行時間變得格外的長,由于事務(wù)可能涉及對后端數(shù)據(jù)庫的鎖定,所以長時間運行的事務(wù)會不必要地占用數(shù)據(jù)庫資源,這時就可以聲明一個事務(wù)在特定時間后自動回滾
由于超時時鐘在一個事務(wù)啟動的時候開始的,因此,有對于那些具有可能啟動一個新事物的傳播行為(PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED)的方法來說,聲明事務(wù)超時才有意義,默認為30S
回滾規(guī)則定義了哪些異常引起回滾,哪些不引起。在默認情況下,事務(wù)只出現(xiàn)運行時異常(Runtime Exception)時回滾,而在出現(xiàn)受檢查異常(Checked Exception)時不回滾。
在本節(jié)中,我們只會考慮事務(wù)的傳播規(guī)則,不會考慮到其隔離級別等其他參數(shù)。測試Service為TestServiceA和TestServiceB
TestSeviceA代碼為:
@Autowiredprivate TransactionDAO transactionDAO;@Autowiredprivate TestServiceB testService;// 會開啟事務(wù),在事務(wù)范圍內(nèi)使用同一個事務(wù),否則開啟新事務(wù)@Transactional(propagation = Propagation.REQUIRES_NEW)public void addOrder() { transactionDAO.insertOrder(); testService.addVoucher(); throw new RuntimeException();}TestServiceB代碼為:
@Autowired private TransactionDAO transactionDAO; @Transactional(propagation = Propagation.REQUIRES_NEW) public void addVoucher() { transactionDAO.insertOrderVoucher();// throw new RuntimeException(); }經(jīng)測試,無論是在addOrder還是addVoucher中拋出異常,數(shù)據(jù)都會提交失敗;當且僅當兩個方法都成功時,數(shù)據(jù)提交成功。
這說明這兩個Service使用的是同一個事務(wù),并且只要方法被調(diào)用就會開啟事務(wù)。
注意:一旦有前置方法開啟了Spring事務(wù),在調(diào)用鏈中的方法即使沒有聲明事務(wù),也都會加入到事務(wù)中。
如果在ServiceA中使用了try catch語句后會是什么情況呢?即代碼如下:
TestSeviceA代碼為:
@Autowiredprivate TransactionDAO transactionDAO;@Autowiredprivate TestServiceB testService;// 會開啟事務(wù),在事務(wù)范圍內(nèi)使用同一個事務(wù),否則開啟新事務(wù)@Transactional(propagation = Propagation.REQUIRED)public void addOrder() { transactionDAO.insertOrder(); try { testService.addVoucher(); } catch (Exception e) { }}TestServiceB代碼為:
@Autowired private TransactionDAO transactionDAO; @Transactional(propagation = Propagation.REQUIRED) public void addVoucher() { transactionDAO.insertOrderVoucher(); throw new RuntimeException(); }在ServiceB中拋出RuntimeException異常,但是在ServiceA中使用try catch捕獲該異常。
執(zhí)行后發(fā)現(xiàn)數(shù)據(jù)沒有提交,但是出現(xiàn)了一個異常:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
原因:在addVoucher()方法return時會將Transactional標記為Rollback only, 而addOrder()方法捕獲了bar的RuntimeException,所以Spring將會commit addOrder()的事務(wù),但是這兩個方法使用的是同一事務(wù),因此在commit addOrder()事務(wù)時,將會拋出UnexpectedRollbackException。
TestServiceA代碼為:
@Autowiredprivate TransactionDAO transactionDAO;@Autowiredprivate TestServiceB testService;// 無論何時都會開啟事務(wù)@Transactional(propagation = Propagation.REQUIRES_NEW)public void addOrder() { transactionDAO.insertOrder(); testService.addVoucher(); throw new RuntimeException();}TestServiceB代碼為:
@Autowired private TransactionDAO transactionDAO; @Transactional(propagation = Propagation.REQUIRES_NEW) public void addVoucher() { transactionDAO.insertOrderVoucher();// throw new RuntimeException(); }經(jīng)測試如果在addOrder中拋出異常,order數(shù)據(jù)不能提交,voucher數(shù)據(jù)被正確提交。相反addVoucher中拋出異常,就只能提交order數(shù)據(jù)。
說明兩個Service是在兩個獨立是事務(wù)中運行,并且只要方法被調(diào)用就開啟事務(wù)。
TestServiceA代碼為:
@Autowiredprivate TransactionDAO transactionDAO;@Autowiredprivate TestServiceB testService;// 自身不會開啟事務(wù),在事務(wù)范圍內(nèi)則使用相同事務(wù),否則不使用事務(wù)@Transactional(propagation = Propagation.SUPPORTS)public void addOrder() { transactionDAO.insertOrder(); testService.addVoucher(); throw new RuntimeException();}TestServiceB代碼為:
@Autowired private TransactionDAO transactionDAO; @Transactional(propagation = Propagation.SUPPORTS) public void addVoucher() { transactionDAO.insertOrderVoucher();// throw new RuntimeException(); }由于兩個方法都被申明為SUPPORTS的傳播規(guī)則,所以即使拋出異常,數(shù)據(jù)都也都能被正確提交。說明兩個方法并沒有使用Spring事務(wù),而是使用本地事務(wù),需要注意的是,這兩個方法的本地事務(wù)并不是同一個事務(wù),一旦出現(xiàn)異常將導致數(shù)據(jù)不一致。
我們來考慮一下更多的組合情況:
- REQUIRED → SUPPORTS 這時開啟事務(wù),并使用同一個事務(wù)- REQUIRES_NEW → SUPPORTS 同a- SUPPORTS → REQUIRED ServiceA由本地事務(wù)管理,ServiceB由Spring事務(wù)管理- SUPPORTS → REQUIRES_NEW 同cTestServiceA代碼為:
@Autowiredprivate TransactionDAO transactionDAO;@Autowiredprivate TestServiceB testService;// 自身不會開啟事務(wù),在事務(wù)范圍內(nèi)使用會掛起事務(wù)執(zhí)行,運行完畢恢復(fù)事務(wù)@Transactional(propagation = Propagation.REQUIRED)public void addOrder() { transactionDAO.insertOrder(); testService.addVoucher();}TestServiceB代碼為:
@Autowired private TransactionDAO transactionDAO; @Transactional(propagation = Propagation.SUPPORTS) public void addVoucher() { transactionDAO.insertOrderVoucher(); throw new RuntimeException(); }經(jīng)測試如果在addVoucher中拋出異常,addOrder數(shù)據(jù)提交失敗,addVoucher數(shù)據(jù)會提交成功。說明ServiceA開了事務(wù),ServiceB沒有開啟事務(wù),而是使用了本地事務(wù)。
TestServiceA代碼
@Autowiredprivate TransactionDAO transactionDAO;@Autowiredprivate TestServiceB testService;// 自身不會開啟事務(wù),必須在事務(wù)環(huán)境使用,否則報錯@Transactional(propagation = Propagation.MANDATORY)public void addOrder() { transactionDAO.insertOrder(); testService.addVoucher();}經(jīng)測試以上代碼報錯
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation ‘mandatory’ 沒有找到事務(wù)環(huán)境
即該注解必須要和能創(chuàng)建事務(wù)的注解使用(PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED)
TestServiceA代碼為:
@Autowiredprivate TransactionDAO transactionDAO;@Autowiredprivate TestServiceB testService;@Transactional(propagation = Propagation.REQUIRED)public void addOrder() { transactionDAO.insertOrder(); testService.addVoucher();}TestServiceB代碼為:
@Autowired private TransactionDAO transactionDAO; // 自身不會開啟事務(wù),在事務(wù)范圍內(nèi)使用拋出異常 @Transactional(propagation = Propagation.NEVER) public void addVoucher() { transactionDAO.insertOrderVoucher(); throw new RuntimeException(); }經(jīng)測試代碼拋出異常
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation ‘never’ 存在事務(wù)環(huán)境
TestServiceA代碼為:
@Autowiredprivate TransactionDAO transactionDAO;@Autowiredprivate TestServiceB testService;// 新開啟一個事務(wù) @Transactional(propagation = Propagation.REQUIRED) public void addOrder() { transactionDAO.insertOrder(); try { testService.addVoucher(); } catch (Exception e) { }// throw new RuntimeException(); }TestServiceB代碼為:
@Autowired private TransactionDAO transactionDAO; // 啟用嵌套事務(wù) @Transactional(propagation = Propagation.NESTED) public void addVoucher() { transactionDAO.insertOrderVoucher(); throw new RuntimeException(); }經(jīng)測試,在ServiceB中拋出異常后,addVoucher數(shù)據(jù)提交失敗,addOrder數(shù)據(jù)提交成功。
如果在ServiceA中拋出異常,那么數(shù)據(jù)都會提交失敗。
https://www.ibm.com/developerworks/cn/education/opensource/os-cn-spring-trans/ https://my.oschina.net/dongli/blog/56904 http://blog.csdn.net/aya19880214/article/details/50640596
新聞熱點
疑難解答