Spring 自呼叫事務失效,你是怎麼解決的?
阿新 • • 發佈:2020-11-09
> **前言**
>
>
> 相信大家都遇到一種事務失效場景,那就是 Spring 自呼叫,就是在 Service 方法內,呼叫另一個加 `@Transactional` 註解的方法,發現事務失效,這時候你是怎麼解決的呢?
>
>
> 公眾號:『 劉志航 』,記錄工作學習中的技術、開發及原始碼筆記;時不時分享一些生活中的見聞感悟。歡迎大佬來指導!
### 事情回顧
那是一個我忘了天氣咋樣的下午,突然蹦出一個小紅點,嗯~ 挺著急的小紅點。
![3E8q0W-3Cg65u](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/3E8q0W-3Cg65u.png)
原來是事務失效了!
莫慌!莫慌!
![kRoqUW-MKbcBj](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/kRoqUW-MKbcBj.png)
![KVdtlZ-F3jW3p](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/KVdtlZ-F3jW3p.png)
![T4poZK-qyiLEV](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/T4poZK-qyiLEV.png)
最後小夥伴選擇了抽走,是我的工具類不香了麼?
![zVf6LC-tLzseS](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/zVf6LC-tLzseS.png)
當然故事的結果是完美的,問題解決了。
![v3W8tg-VJEgZS](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/v3W8tg-VJEgZS.jpg)
### 事務
在開發中涉及到同時操作多個表的時候,要保證兩個操作要麼一起成功,要麼一起失敗,這時候就需要用到事務。
現在一般使用的都是基於 `@Transactional` 註解的**宣告式事務**。
而事務使用過程中有以下幾個注意事項:
1. 事務只能應用到 public 方法上才會有效;
2. 事務需要從外部呼叫,Spring 自呼叫會失效;
3. 建議事務註解 @Transactional 一般新增在實現類上。
當然這幾句話不是說我的,人家官方文件可是明確說明的!
![IlNXVn-uemNYF](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/IlNXVn-uemNYF.png)
這裡可是說明了`應僅將 @Transactional 註解應用於具有公開可見性的方法。如果對受 protected, private o或 package-visible 修飾的方法使用,則不會引發任何錯誤,但是被註解的方法不會顯示已配置的事務設定。`
說白了,就是你用了,不會報錯,但是不生效!
![M5XTck-YnrPiu](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/M5XTck-YnrPiu.png)
至於建議加在實現類上,這個只是建議,不過如果加在介面類或介面方法上時,只有配置基於介面的代理才會生效。所以這塊還是老老實實的`加在實現類或實現類方法上`吧。
![P28Ciu-4YvetS](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/P28Ciu-4YvetS.png)
因為代理模式只攔截通過代理傳入的外部方法呼叫,所以自呼叫事務是不生效的。
官方的解釋還是比較簡單明瞭的,雖然我看不懂,但是不影響我截圖。
那我還是再截一個吧……
![sBkeIz-80K1UE](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/sBkeIz-80K1UE.png)
### 實際使用
但是在開發中,小夥伴們往往會遇到這種情況!
![g1BG6s-BgQfOw](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/g1BG6s-BgQfOw.png)
本來**自己寫的**程式碼就一坨坨的又臭又長,裡面有各種驗籤、驗參、查詢、驗證等等,就想著來個事務,讓事務包裹的範圍最小,僅僅在同時更新的時候加上事務吧!
![38SYyX-gEXV58](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/38SYyX-gEXV58.png)
這麼寫,咦~ IDEA 報錯了,好像不能 `private` 修飾,那我改成 `public`。
很顯然事務是不生效的。
把更新的程式碼放到`又臭又長`的程式碼裡面,讓它變得更臭更長,然後用 `@Transactional` 註解一加。完美解決!
![9r8ioC-W6kSbv](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/9r8ioC-W6kSbv.jpg)
請放過那坨程式碼吧!來看看下面的辦法。
#### 解決方案 1
![08fPAy-ee1X6A](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/08fPAy-ee1X6A.png)
那我改成外部呼叫不就行了麼?
再宣告一個 Service,把更新表的邏輯放過去。
我一般就喜歡使用這個辦法。
#### 解決方案 2
使用`程式設計式事務`,前面說了,使用`宣告式事務`時,又這又那,我換一種總可以吧!
![yuJKad-LGJuVt](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/yuJKad-LGJuVt.png)
你看,我還把方法改成 `private` 修飾了,事務也生效。完美解決!
![8orQs4-tgtQts](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/8orQs4-tgtQts.jpg)
其實這個方法也很不錯哦!
#### 解決方案 3
又想用註解,又想自呼叫怎麼辦?
![MBSeHo-E5RDuC](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/MBSeHo-E5RDuC.jpg)
不過... 麻煩一點還是可以的。
咱們可以參考`程式設計式事務`的方式,不就是不讓自呼叫麼,我調外部方法,然後外部方法再給我調回來不就可以了。
```java
@Component
public class TransactionalComponent {
public interface Cell {
void run() throws Exception;
}
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void required(Cell cell) throws Exception {
cell.run();
}
}
```
這樣的話不就可以通過 `TransactionalComponent` 呼叫了麼,並且還可以使用 `lambda` 表示式。
![y5bnw5-TMtjKN](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/y5bnw5-TMtjKN.png)
當然基於這個版本也可以做一個迭代,就是使用靜態方法呼叫,不用每次都用 `@Autowired` 注入一次。
```java
public class TransactionalUtils {
private static volatile TransactionalComponent transactionalComponent;
private static synchronized TransactionalComponent getTransactionalComponent() {
if (transactionalComponent == null) {
// 從容器中獲取 transactionalComponent
transactionalComponent = ApplicationContextUtils.getBean(TransactionalComponent.class);
}
return transactionalComponent;
}
public static void required(TransactionalComponent.Cell cell) throws Exception {
getTransactionalComponent().required(cell);
}
}
```
![awzH4i-JjJNp4](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/awzH4i-JjJNp4.png)
這樣通過工具類 `TransactionalUtils` 便可以直接呼叫靜態方法的方式執行事務操作。
### 總結
#### 結束語
本文主要介紹為什麼會遇到事務失效,以及事務失效的避免方式,同時提供了三種方式來解決自呼叫事務失效的問題。不足之處,歡迎指正。
#### 相關資料
1. Spring 文件:[https://docs.spring.io/spring-framework/docs/5.3.0/reference/html/data-access.html#transaction-declarative-annotations](https://docs.spring.io/spring-framework/docs/5.3.0/reference/html/data-access.html#transaction-declarative-anno