面試突擊87:說一下 Spring 事務傳播機制?
我報名參加金石計劃1期挑戰——瓜分10萬獎池,這是我的第8篇文章,點選檢視活動詳情
Spring 事務傳播機制是指,包含多個事務的方法在相互呼叫時,事務是如何在這些方法間傳播的。
既然是“事務傳播”,所以事務的數量應該在兩個或兩個以上,Spring 事務傳播機制的誕生是為了規定多個事務在傳播過程中的行為的。比如方法 A 開啟了事務,而在執行過程中又呼叫了開啟事務的 B 方法,那麼 B 方法的事務是應該加入到 A 事務當中呢?還是兩個事務相互執行互不影響,又或者是將 B 事務巢狀到 A 事務中執行呢?所以這個時候就需要一個機制來規定和約束這兩個事務的行為,這就是 Spring 事務傳播機制所解決的問題。
Spring 事務傳播機制有哪些?
Spring 事務傳播機制可使用 @Transactional(propagation=Propagation.REQUIRED) 來定義,Spring 事務傳播機制的級別包含以下 7 種:
- Propagation.REQUIRED:預設的事務傳播級別,它表示如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。
- Propagation.SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續執行。
- Propagation.MANDATORY:(mandatory:強制性)如果當前存在事務,則加入該事務;如果當前沒有事務,則丟擲異常。
- Propagation.REQUIRES_NEW:表示建立一個新的事務,如果當前存在事務,則把當前事務掛起。也就是說不管外部方法是否開啟事務,Propagation.REQUIRES_NEW 修飾的內部方法會新開啟自己的事務,且開啟的事務相互獨立,互不干擾。
- Propagation.NOT_SUPPORTED:以非事務方式執行,如果當前存在事務,則把當前事務掛起。
- Propagation.NEVER:以非事務方式執行,如果當前存在事務,則丟擲異常。
- Propagation.NESTED:如果當前存在事務,則建立一個事務作為當前事務的巢狀事務來執行;如果當前沒有事務,則該取值等價於 PROPAGATION_REQUIRED。
以上 7 種傳播機制,可根據“是否支援當前事務”的維度分為以下 3 類:
看到這裡,有人可能會說:說了這麼多,我也看不懂啊,即使看懂了,我也記不住啊?這要咋整?
沒關係,接下來我們用一個例子,來說明這 3 類事務傳播機制的區別。
以情侶之間是否要買房為例,我們將以上 3 類事務傳播機制可以看作是戀愛中的 3 類女生型別:
- 普通型
- 強勢型
- 懂事型
這三類女生如下圖所示:
支援當前事務的“女生”,這裡的事務指的是“房子”,它分為 3 種(普通型女生):
- Propagation.REQUIRED(需要有房子):有房子了咱們一起住,沒房子了咱們一起賺錢買房子。
- Propagation.SUPPORTS(可以有房子):有房子了就一起住,沒房子了咱們就一起租房子。
- Propagation.MANDATORY(強制有房子):有房子了就一起住,沒房子了就分手。
不支援當前事務的“女生”也分為 3 種(強勢型或者叫事業型):
- Propagation.REQUIRES_NEW:不要你的房子,必須一起賺錢買房子。
- Propagation.NOT_SUPPORTED:不要你的房子,必須一起租房子。
- Propagation.NEVER:必須一起租房子,你有房子就分手。
最後一種是巢狀性事務 Propagation.NESTED,它屬於懂事型女友,如果有房子了就以房子為基礎做點小生意,賣個花生、水果啥的,如果買賣成了,那就繼續發展;如果失敗了,至少還有房子;如果沒房子也沒關係,一起賺錢買房子。
事務傳播機制使用與演示
接下來我們演示一下事務傳播機制的使用,以下面 3 個最典型的事務傳播級別為例:
- 支援當前事務的 REQUIRED;
- 不支援當前事務的 REQUIRES_NEW;
- 巢狀事務 NESTED。
下來我們分別來看。
事務傳播機制的示例,需要用到以下兩張表:
``sql
-- 使用者表
CREATE TABLE
user(
idint(11) NOT NULL AUTO_INCREMENT,
namevarchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
passwordvarchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
createtimedatetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (
id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC;
-- 日誌表
CREATE TABLE log
(
id
int(11) NOT NULL AUTO_INCREMENT,
content
text NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
```
建立一個 Spring Boot 專案,核心業務程式碼有 3 個:UserController、UserServcie 以及 LogService。在 UserController 裡面呼叫 UserService 新增使用者,並呼叫 LogService 新增日誌。
REQUIRED 使用演示
REQUIRED 支援當前事務。 UserController 實現程式碼如下,其中 save 方法開啟了事務:
```java @RestController public class UserController { @Resource private UserService userService; @Resource private LogService logService;
@RequestMapping("/save")
@Transactional
public Object save(User user) {
// 插入使用者操作
userService.save(user);
// 插入日誌
logService.saveLog("使用者插入:" + user.getName());
return true;
}
} ```
UserService 實現程式碼如下:
```java @Service public class UserService { @Resource private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRED)
public int save(User user) {
return userMapper.save(user);
}
} ```
LogService 實現程式碼如下:
```java @Service public class LogService { @Resource private LogMapper logMapper;
@Transactional(propagation = Propagation.REQUIRED)
public int saveLog(String content) {
// 出現異常
int i = 10 / 0;
return logMapper.saveLog(content);
}
} ```
執行結果:程式報錯,兩張表中都沒有插入任何資料。
執行流程描述:
- 首先 UserService 中的新增使用者方法正常執行完成。
- LogService 儲存日誌程式報錯,因為使用的是 UserController 中的全域性事務,所以整個事務回滾,步驟 1 中的操作也跟著回滾。
- 所以資料庫中沒有新增任何資料。
REQUIRED_NEW 使用演示
REQUIRED_NEW 不支援當前事務。 UserController 實現程式碼:
java
@RequestMapping("/save")
@Transactional
public Object save(User user) {
// 插入使用者操作
userService.save(user);
// 插入日誌
logService.saveLog("使用者插入:" + user.getName());
return true;
}
UserService 實現程式碼:
```java @Service public class UserService { @Resource private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int save(User user) {
System.out.println("執行 save 方法.");
return userMapper.save(user);
}
} ```
LogService 實現程式碼:
```java @Service public class LogService { @Resource private LogMapper logMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int saveLog(String content) {
// 出現異常
int i = 10 / 0;
return logMapper.saveLog(content);
}
} ```
程式執行結果:
User 表中成功添加了一條使用者資料,Log 表執行失敗,沒有加入任何資料,但它並沒有影響到 UserController 中的事務執行。
通過以上結果可以看出:LogService 中使用的是單獨的事務,雖然 LogService 中的事務執行失敗了,但並沒有影響 UserController 和 UserService 中的事務。
NESTED 使用演示
NESTED 是巢狀事務。 UserController 實現程式碼如下:
java
@RequestMapping("/save")
@Transactional
public Object save(User user) {
// 插入使用者操作
userService.save(user);
return true;
}
UserService 實現程式碼如下:
java
@Transactional(propagation = Propagation.NESTED)
public int save(User user) {
int result = userMapper.save(user);
System.out.println("執行 save 方法.");
// 插入日誌
logService.saveLog("使用者插入:" + user.getName());
return result;
}
LogService 實現程式碼如下:
java
@Transactional(propagation = Propagation.NESTED)
public int saveLog(String content) {
// 出現異常
int i = 10 / 0;
return logMapper.saveLog(content);
}
最終執行結果,使用者表和日誌表都沒有新增任何資料。
執行流程描述:
- UserController 中呼叫了 UserService 的新增使用者方法,UserService 使用 NESTED 迴圈巢狀事務,併成功執行了新增使用者的方法。
- UserService 中呼叫了 LogService 的新增方法,LogService 使用了 NESTED 迴圈巢狀事務,但在方法執行中出現的異常,因此回滾了當前事務。
- 因為 UserService 使用的是巢狀事務,所以發生回滾的事務是全域性的,也就是說 UserService 中的新增使用者方法也被回滾了,最終執行結果是使用者表和日誌表都沒有新增任何資料。
總結
Spring 事務傳播機制是包含多個事務的方法在相互呼叫時,事務是如何在這些方法間傳播的。事務的傳播級別有 7 個,支援當前事務的:REQUIRED、SUPPORTS、MANDATORY;不支援當前事務的:REQUIRES_NEW、NOT_SUPPORTED、NEVER,以及巢狀事務 NESTED,其中 REQUIRED 是預設的事務傳播級別。
是非審之於己,譭譽聽之於人,得失安之於數。
公眾號:Java面試真題解析
- 面試官:什麼是雙親委派模型?
- 面試官:熔斷和降級有什麼區別?
- 寬表為什麼橫行?
- 有沒有完全自主的國產化資料庫技術
- 面試突擊64:瞭解 HTTP 協議嗎?
- 面試突擊80:說一下 Spring 中 Bean 的生命週期?
- 面試突擊89:事務隔離級別和傳播機制有什麼區別?
- 面試突擊82:SpringBoot 中如何操作事務?
- 面試突擊87:說一下 Spring 事務傳播機制?
- 面試突擊81:什麼是跨域問題?如何解決?
- 面試突擊71:GET 和 POST 有什麼區別?
- 面試突擊70:什麼是粘包和半包?怎麼解決?
- 面試突擊68:為什麼 TCP 需要 3 次握手?
- 面試突擊66:請求轉發和請求重定向有什麼區別?
- 面試突擊63:MySQL 中如何去重?
- 面試突擊65:為什麼要用HTTPS?它有什麼優點?
- 面試突擊62:group by 有哪些注意事項?
- 面試突擊53:常見的 HTTP 狀態碼有哪些?
- 面試突擊61:說一下MySQL事務隔離級別?
- 面試突擊52:什麼是三正規化?它有什麼用?