Spring 事務傳播機制是指,包含多個事務的方法在相互呼叫時,事務是如何在這些方法間傳播的。
既然是「事務傳播」,所以事務的數量應該在兩個或兩個以上,Spring 事務傳播機制的誕生是為了規定多個事務在傳播過程中的行為的。比如方法 A 開啟了事務,而在執行過程中又呼叫了開啟事務的 B 方法,那麼 B 方法的事務是應該加入到 A 事務當中呢?還是兩個事務相互執行互不影響,又或者是將 B 事務巢狀到 A 事務中執行呢?所以這個時候就需要一個機制來規定和約束這兩個事務的行為,這就是 Spring 事務傳播機制所解決的問題。
Spring 事務傳播機制可使用 @Transactional(propagation=Propagation.REQUIRED) 來定義,Spring 事務傳播機制的級別包含以下 7 種:
以上 7 種傳播機制,可根據「是否支援當前事務」的維度分為以下 3 類:
看到這裡,有人可能會說:說了這麼多,我也看不懂啊,即使看懂了,我也記不住啊?這要咋整?
沒關係,接下來我們用一個例子,來說明這 3 類事務傳播機制的區別。
以情侶之間是否要買房為例,我們將以上 3 類事務傳播機制可以看作是戀愛中的 3 類女生型別:
這三類女生如下圖所示:
支援當前事務的「女生」,這裡的事務指的是「房子」,它分為 3 種(普通型女生):
不支援當前事務的「女生」也分為 3 種(強勢型或者叫事業型):
最後一種是巢狀性事務 Propagation.NESTED,它屬於懂事型女友,如果有房子了就以房子為基礎做點小生意,賣個花生、水果啥的,如果買賣成了,那就繼續發展;如果失敗了,至少還有房子;如果沒房子也沒關係,一起賺錢買房子。
接下來我們演示一下事務傳播機制的使用,以下面 3 個最典型的事務傳播級別為例:
下來我們分別來看。
事務傳播機制的範例,需要用到以下兩張表:
-- 使用者表
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`createtime` datetime 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 支援當前事務。
UserController 實現程式碼如下,其中 save 方法開啟了事務:
@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 實現程式碼如下:
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRED)
public int save(User user) {
return userMapper.save(user);
}
}
LogService 實現程式碼如下:
@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 不支援當前事務。
UserController 實現程式碼:
@RequestMapping("/save")
@Transactional
public Object save(User user) {
// 插入使用者操作
userService.save(user);
// 插入紀錄檔
logService.saveLog("使用者插入:" + user.getName());
return true;
}
UserService 實現程式碼:
@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 實現程式碼:
@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 是巢狀事務。
UserController 實現程式碼如下:
@RequestMapping("/save")
@Transactional
public Object save(User user) {
// 插入使用者操作
userService.save(user);
return true;
}
UserService 實現程式碼如下:
@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 實現程式碼如下:
@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面試真題解析