一, 事務的一些基礎知識簡單回顧一下,講的不是很深入,網上博客很多。
1,關於事務的四大特性:原子性、隔離性、一致性、持久性 本文不再贅述;
2,事務的隔離級別:讀未提交,讀已提交,可重復讀,串行化(這里應該深入了解各個級別會出現什么問題,比如臟讀,不可重復讀,幻讀)
3,事務的傳播行為:事務傳播行為指的就是當一個事務方法被另一個事務方法調用時,這個事務方法應該如何進行。 例如:methodA事務方法調用methodB事務方法時,methodB是繼續在調用者methodA的事務中運行呢,還是為自己開啟一個新事務運行,這就是由methodB的事務傳播行為決定的。默認采用:PROPAGATION_REQUIRED
二,接下來我們簡單回顧一下java的異常體系:
Throwable 是 Java 語言中所有錯誤或異常的超類,在 Java 中只有 Throwable 類型的實例才可以被拋出(throw)或者捕獲(catch),它是異常處理機制的基本組成類型。
實例分為 Error 和 Exception 兩種。
Error 類是指 java 運行時系統的內部錯誤和資源耗盡錯誤。應用程序不會拋出該類對象。如果
出現了這樣的錯誤,除了告知用戶,剩下的就是盡力使程序安全的終止。
Exception 又有兩個分支 , 一個是運行時異常 RuntimeException , 一 個是檢查異常 CheckedException。
RuntimeException 如 :NullPointerException 、 ClassCastException ;
CheckedException 如: I/O 錯誤導致的 IOException、SQLException。
RuntimeException 是那些可能在 Java 虛擬機正常運行期間拋出的異常的超類。 如果出現 RuntimeException,那么一
定是程序員代碼書寫導致的錯誤.
CheckedException:一般是外部錯誤,這種異常都發生在編譯階段,Java 編譯器會強
制程序去捕獲此類異常,即會出現要求你把這段可能出現異常的程序進行 try catch
三,言歸正傳,項目中事務的使用
springboot自動配置默認為我們配置好了事務管理器,參考我們另一篇文章https://www.cnblogs.com/enchaolee/p/11364025.html
我們在項目的實際開發中,對於事務的處理,在編碼中無外乎使用@Transactional這個注解,以及對於異常的處理,下面我們詳細說一下。
1,方法上加入@Transactional注解后,這個方法就成為了事務方法,如果對於注解的一些屬性不做特殊配置的話,方法中如果出現了RuntimeException(運行時異常),事務會進行回滾,如果出現了checkedException(編譯時異常),則不會回滾。那么對於這類異常,我們想讓他在拋出的時候也進行回滾怎么辦呢,當我們點進去@Transactional注解后,看下圖:
我們可以看到,圖中有標紅的兩個注解中的屬性,我們可以在這里進行手動配置,以實現方法拋出具體異常進行回滾的邏輯。例如我們可以這樣配置:@Transactional(rollbackFor = Exception.class);
當然還有兩個字段可以設置拋出某某異常不進行回滾:noRollbackFor,noRollbackForClassName;
2,對於異常捕獲,事務如何處理
如果我們在事務方法中,手動捕獲了異常,並沒有讓事務拋出去,也沒有手動指定需要回滾,那么事務方法即使出現異常,也會提交事務。
比如我們這樣去做,只是記錄了日志,即使使用rollbackfor=Exception.class制定了需要回滾的邏輯,但是事務仍然會提交,除非加上下面這一行:
有這句代碼就不需要再手動拋出運行時異常了,但是不建議這樣做,因為這樣做代碼中會多出很多事務回滾的代碼,不利於維護,還是交得spring去處理更妥當。3,的
3,我們該怎么樣處理異常回滾呢?
如圖所示,CommonException是我們定義的全局異常類,我們可以把catch到的異常統一按照一定的格式進行拋出。下面是CommonException相關代碼

1 public class CommonException extends RuntimeException { 2 3 protected String errMsg; // 錯誤提示信息,顯示給用戶 4 protected String detailMsg; // 錯誤的具體信息,可能包含一些參數ID等信息 5 protected CommonErrorCode error; // 錯誤碼 6 protected Object data; // 返回內容 7 8 public CommonException(CommonErrorCode error) { 9 super(error.getDesc()); 10 this.error = error; 11 this.errMsg = error.getDesc(); 12 this.detailMsg = error.getDesc(); 13 } 14 15 public CommonException(CommonErrorCode error, String errMsg) { 16 super(errMsg); 17 this.error = error; 18 this.errMsg = errMsg; 19 this.detailMsg = errMsg; 20 } 21 22 public CommonException(CommonErrorCode error, String errMsg, String detailMsg) { 23 super(StringUtils.isEmpty(detailMsg) ? errMsg : detailMsg); 24 this.error = error; 25 this.errMsg = errMsg; 26 this.detailMsg = detailMsg; 27 } 28 29 public CommonException(CommonErrorCode error, String errMsg, Throwable cause) { 30 super(errMsg, cause); 31 this.error = error; 32 this.errMsg = errMsg; 33 this.detailMsg = errMsg; 34 } 35 36 public CommonException(CommonErrorCode error, String errMsg, String detailMsg, Throwable cause) { 37 super(StringUtils.isEmpty(detailMsg) ? errMsg : detailMsg, cause); 38 this.error = error; 39 this.errMsg = errMsg; 40 this.detailMsg = detailMsg; 41 } 42 43 public CommonException(CommonErrorCode error, Throwable cause) { 44 super(error.getDesc(), cause); 45 this.error = error; 46 this.errMsg = error.getDesc(); 47 this.detailMsg = error.getDesc(); 48 } 49 50 public String getErrMsg() { 51 return errMsg; 52 } 53 54 public CommonErrorCode getError() { 55 return error; 56 } 57 58 public String getDetailMsg() { 59 return detailMsg; 60 } 61 62 public void setDetailMsg(String detailMsg) { 63 this.detailMsg = detailMsg; 64 } 65 66 public void setErrMsg(String errMsg) { 67 this.errMsg = errMsg; 68 } 69 70 public Object getData() { 71 return data; 72 } 73 74 public CommonException setData(Object data) { 75 this.data = data; 76 return this; 77 } 78 }
4,事務方法調用事務方法,怎么去處理
首先我們先確認一個前提:事務的傳播行為我們使用默認的即:required,並且我們假設有兩個事務方法a,b;a調用b。
(1)根據上面我們講過的required的特性,我們知道spring對於這種事務方法間的調用,會默認把它當做一個事務;我們假設如果b中拋出了NullpointException,並且a,b都沒有做異常的處理,那么由a,b組成的整個事務肯定都會進行回滾,這是毋庸置疑的。
(2)如果b出現異常,a catch了異常,並且沒有拋出去,就像我們上面例子講的只是記錄了日志,我們會發現這個異常,思考一下為什么呢?
我們知道spring 事務管理,啟用事務的方法,調用另一個事務方法時,會進行事務傳播。注解@Transactional默認的傳播機制是PROPAGATION_REQUIRED。再來回顧一下required特性:表示當前方法必須運行在事務中。如果當前事務存在,方法將會在該事務中運行。否則,會啟動一個新的事務
所以是補齊方法運行的時候,同步的事務合並到了補齊的事務里面。當同步發票發生異常后,被try catch 捕獲,沒有拋出來。但是事務還是會進行回滾,回滾執行到 DataSourceTransactionManager 類的 doSetRollbackOnly 方法時,設置了rollbackOnly = true;
由於異常被catch, 不阻斷整個事務執行。整個事務執行完后,執行commit 提交,我們打開這個抽象類AbstractPlatformTransactionManager看一下commit的邏輯
這里我標紅了這個方法,我們繼續往下看,我們打開DefaultTransactionStatus這個類,可以看到標紅方法的具體實現。
在執行commit 提交邏輯時,執行到 DefaultTransactionStatus 類的 isGlobalRollbackOnly方法時,判斷rollbackOnly 為true, 則執行回滾,並且打出那句報錯的日志”Transaction rolled back because it has been marked as rollback-only”。
5,需要注意的一些點
(1)事務方法需要標記為public的
(2)@Transactional注解只能寫在service中,不能再controller中,否則會報404錯誤
(3)如果在使用事務的情況下,所有操作都是讀操作,那建議把事務設置成只讀事務,當事務被標識為只讀事務時,Spring可以對某些可以針對只讀事務進行優化的資源就可以執行相應的優化措施,需要手動設置成true。但是方法再執行增刪改回拋異常。