org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
后來才發現,我這個問題在ssh或者ssm之類的框架下其實是具有一定的普遍性的。上述異常一般都會出現在下述java偽代碼描述的場景中:
在ssh或者ssm等框架集成的項目中,通常service層的事務是由spring代理的。service層的方法發生或拋出異常,則事務會回滾,導致無法提交。通常,我們的項目都會設有全局的異常處理機制,一旦發生異常,會有全局的異常處理機制進行統一處理,所以一般不需要在代碼中主動捕獲異常。
然而,某些特殊的場景中,比如下面的業務層serviceA具有方法methodA,methodA會調用另一個業務層serviceB的方法methodB。在methodA的實際執行過程中,假如methodB有可能拋出異常,但在methodB無論執行是否正常都不能影響methodA的執行的情況下,通常需要methodA主動捕獲這個異常。在methodA主動捕獲這個異常的情況下,只要methodA的其他代碼不拋出異常,則methodA是不會拋出任何異常的。既然methodA不會拋出異常,道理上講作用於methodA上的事務是應該可以正常提交的呀,對不對?然而事實上,一旦methodB拋出異常,不管methodA的其他代碼是否正確執行,整個事務是無法提交的(說這句話的前提是methodA和methodB上都具有事務,並且都由spring進行統一的事務管理)。
serviceA.methodA()
{
doSomethingA();
try { serviceB.methodB{}; //這里面有異常標記為回滾, doSetRollbackOnly(status); } catch { //捕獲異常轉到commit時,由於已經標記為要回滾, 回滾並拋出新異常 } doSomethingB(); }
出現上述異常是因為自己之前沒有很好的理解spring事務的機制。 上述的methodA被調用執行時,有兩個點是被spring事務代理的。也即serviceA.methodA()和serviceB.methodB(),這兩個方法中只要有異常事件將回滾。 上述場景中存在事務嵌套,如果methodA中有異常出現事務會直接回滾,但methodB中有異常只是標記狀態為需要回滾,最終在methodA中回滾。 上述場景中methodB有異常事務被標記為回滾,可是被methodA捕獲了,也就不回滾了,一直執行到最后commit。在commit時spring會判斷回滾標志,若檢測到存在回滾標記, 則回滾事務並拋出UnexpectedRollbackException異常。
針對上述事務無法提交的解決辦法,本人現在總結了兩種處理方法,有路過的看到過的,要是還有什么較好的解決方法,不妨給本人留言,大家共同探討,一起進步吧!言歸正傳,本人的總結出的這兩種解決方法描述如下:
-
serviceB.methodB不聲明為事務代理。 但是很多時候serviceB.methodB也被其他地方使用,並且是需要事務管理的。這時候可以重寫一個方法。但是要注意這個方法中數據的一致性。
-
和1一樣也是重寫一個serviceB.methodB方法,但在里面不拋出異常,而是將異常轉化為一個布爾值並返回,這個返回的布爾值相當於一個標記methodB是否在執行過程中出現異常的狀態值。methodA可以根據這個返回的狀態值判斷后續代碼是否有必要繼續執行。這樣,也可以避免上述異常的產生。
