1. 程式人生 > >spring事務在實際專案開發中的使用

spring事務在實際專案開發中的使用

  一, 事務的一些基礎知識簡單回顧一下,講的不是很深入,網上部落格很多。

   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 }
View Code

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。但是方法再執行增刪改回拋異常。

&n