1. 程式人生 > >本人遇到的spring事務之UnexpectedRollbackException異常解決筆記

本人遇到的spring事務之UnexpectedRollbackException異常解決筆記

本人最近在使用spring事務管理的過程中遇到如下異常,導致服務端丟擲500給前端,讓搞前端的哥們抱怨我心裡著實不爽,前前後後折騰了近半個小時才得於解決,今天就做個筆記,以免日後又犯這個錯誤。好了,錯誤是這樣的:

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異常。

針對上述事務無法提交的解決辦法,本人現在總結了兩種處理方法,有路過的看到過的,要是還有什麼較好的解決方法,不妨給本人留言,大家共同探討,一起進步吧!言歸正傳,本人的總結出的這兩種解決方法描述如下:

  1. serviceB.methodB不宣告為事務代理。 但是很多時候serviceB.methodB也被其他地方使用,並且是需要事務管理的。這時候可以重寫一個方法。但是要注意這個方法中資料的一致性。

  2. 和1一樣也是重寫一個serviceB.methodB方法,但在裡面不丟擲異常,而是將異常轉化為一個布林值並返回,這個返回的布林值相當於一個標記methodB是否在執行過程中出現異常的狀態值。methodA可以根據這個返回的狀態值判斷後續程式碼是否有必要繼續執行。這樣,也可以避免上述異常的產生。