面試突擊:說一下 Spring 事務(wù)傳播機(jī)制?
Spring 事務(wù)傳播機(jī)制是指,包含多個(gè)事務(wù)的方法在相互調(diào)用時(shí),事務(wù)是如何在這些方法間傳播的。
既然是“事務(wù)傳播”,所以事務(wù)的數(shù)量應(yīng)該在兩個(gè)或兩個(gè)以上,Spring 事務(wù)傳播機(jī)制的誕生是為了規(guī)定多個(gè)事務(wù)在傳播過程中的行為的。比如方法 A 開啟了事務(wù),而在執(zhí)行過程中又調(diào)用了開啟事務(wù)的 B 方法,那么 B 方法的事務(wù)是應(yīng)該加入到 A 事務(wù)當(dāng)中呢?還是兩個(gè)事務(wù)相互執(zhí)行互不影響,又或者是將 B 事務(wù)嵌套到 A 事務(wù)中執(zhí)行呢?所以這個(gè)時(shí)候就需要一個(gè)機(jī)制來規(guī)定和約束這兩個(gè)事務(wù)的行為,這就是 Spring 事務(wù)傳播機(jī)制所解決的問題。
Spring 事務(wù)傳播機(jī)制有哪些?
Spring 事務(wù)傳播機(jī)制可使用 @Transactional(propagation=Propagation.REQUIRED) 來定義,Spring 事務(wù)傳播機(jī)制的級(jí)別包含以下 7 種:
- Propagation.REQUIRED:默認(rèn)的事務(wù)傳播級(jí)別,它表示如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則創(chuàng)建一個(gè)新的事務(wù)。
- Propagation.SUPPORTS:如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則以非事務(wù)的方式繼續(xù)運(yùn)行。
- Propagation.MANDATORY:(mandatory:強(qiáng)制性)如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則拋出異常。
- Propagation.REQUIRES_NEW:表示創(chuàng)建一個(gè)新的事務(wù),如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起。也就是說不管外部方法是否開啟事務(wù),Propagation.REQUIRES_NEW 修飾的內(nèi)部方法會(huì)新開啟自己的事務(wù),且開啟的事務(wù)相互獨(dú)立,互不干擾。
- Propagation.NOT_SUPPORTED:以非事務(wù)方式運(yùn)行,如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起。
- Propagation.NEVER:以非事務(wù)方式運(yùn)行,如果當(dāng)前存在事務(wù),則拋出異常。
- Propagation.NESTED:如果當(dāng)前存在事務(wù),則創(chuàng)建一個(gè)事務(wù)作為當(dāng)前事務(wù)的嵌套事務(wù)來運(yùn)行;如果當(dāng)前沒有事務(wù),則該取值等價(jià)于PROPAGATION_REQUIRED。
以上 7 種傳播機(jī)制,可根據(jù)“是否支持當(dāng)前事務(wù)”的維度分為以下 3 類:
看到這里,有人可能會(huì)說:說了這么多,我也看不懂啊,即使看懂了,我也記不住?。窟@要咋整?
沒關(guān)系,接下來我們用一個(gè)例子,來說明這 3 類事務(wù)傳播機(jī)制的區(qū)別。
以情侶之間是否要買房為例,我們將以上 3 類事務(wù)傳播機(jī)制可以看作是戀愛中的 3 類女生類型:
- 普通型
- 強(qiáng)勢(shì)型
- 懂事型
這三類女生如下圖所示:
支持當(dāng)前事務(wù)的“女生”,這里的事務(wù)指的是“房子”,它分為 3 種(普通型女生):
- Propagation.REQUIRED(需要有房子):有房子了咱們一起住,沒房子了咱們一起賺錢買房子。
- Propagation.SUPPORTS(可以有房子):有房子了就一起住,沒房子了咱們就一起租房子。
- Propagation.MANDATORY(強(qiáng)制有房子):有房子了就一起住,沒房子了就分手。
不支持當(dāng)前事務(wù)的“女生”也分為 3 種(強(qiáng)勢(shì)型或者叫事業(yè)型):
- Propagation.REQUIRES_NEW:不要你的房子,必須一起賺錢買房子。
- Propagation.NOT_SUPPORTED:不要你的房子,必須一起租房子。
- Propagation.NEVER:必須一起租房子,你有房子就分手。
最后一種是嵌套性事務(wù) Propagation.NESTED,它屬于懂事型女友,如果有房子了就以房子為基礎(chǔ)做點(diǎn)小生意,賣個(gè)花生、水果啥的,如果買賣成了,那就繼續(xù)發(fā)展;如果失敗了,至少還有房子;如果沒房子也沒關(guān)系,一起賺錢買房子。
事務(wù)傳播機(jī)制使用與演示
接下來我們演示一下事務(wù)傳播機(jī)制的使用,以下面 3 個(gè)最典型的事務(wù)傳播級(jí)別為例:
- 支持當(dāng)前事務(wù)的 REQUIRED;
- 不支持當(dāng)前事務(wù)的 REQUIRES_NEW;
- 嵌套事務(wù) NESTED。
下來我們分別來看。
事務(wù)傳播機(jī)制的示例,需要用到以下兩張表:
-- 用戶表
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;
創(chuàng)建一個(gè) Spring Boot 項(xiàng)目,核心業(yè)務(wù)代碼有 3 個(gè):UserController、UserServcie 以及 LogService。在 UserController 里面調(diào)用 UserService 添加用戶,并調(diào)用 LogService 添加日志。
REQUIRED 使用演示
REQUIRED 支持當(dāng)前事務(wù)。
UserController 實(shí)現(xiàn)代碼如下,其中 save 方法開啟了事務(wù):
public class UserController {
private UserService userService;
private LogService logService;
("/save")
public Object save(User user) {
// 插入用戶操作
userService.save(user);
// 插入日志
logService.saveLog("用戶插入:" + user.getName());
return true;
}
}
UserService 實(shí)現(xiàn)代碼如下:
public class UserService {
private UserMapper userMapper;
(propagation = Propagation.REQUIRED)
public int save(User user) {
return userMapper.save(user);
}
}
LogService 實(shí)現(xiàn)代碼如下:
public class LogService {
private LogMapper logMapper;
(propagation = Propagation.REQUIRED)
public int saveLog(String content) {
// 出現(xiàn)異常
int i = 10 / 0;
return logMapper.saveLog(content);
}
}
執(zhí)行結(jié)果:程序報(bào)錯(cuò),兩張表中都沒有插入任何數(shù)據(jù)。
執(zhí)行流程描述:
- 首先 UserService 中的添加用戶方法正常執(zhí)行完成。
- LogService 保存日志程序報(bào)錯(cuò),因?yàn)槭褂玫氖?UserController 中的全局事務(wù),所以整個(gè)事務(wù)回滾,步驟 1 中的操作也跟著回滾。
- 所以數(shù)據(jù)庫中沒有添加任何數(shù)據(jù)。
REQUIRED_NEW 使用演示
REQUIRED_NEW 不支持當(dāng)前事務(wù)。
UserController 實(shí)現(xiàn)代碼:
"/save")(
public Object save(User user) {
// 插入用戶操作
userService.save(user);
// 插入日志
logService.saveLog("用戶插入:" + user.getName());
return true;
}
UserService 實(shí)現(xiàn)代碼:
public class UserService {
private UserMapper userMapper;
(propagation = Propagation.REQUIRES_NEW)
public int save(User user) {
System.out.println("執(zhí)行 save 方法.");
return userMapper.save(user);
}
}
LogService 實(shí)現(xiàn)代碼:
public class LogService {
private LogMapper logMapper;
(propagation = Propagation.REQUIRES_NEW)
public int saveLog(String content) {
// 出現(xiàn)異常
int i = 10 / 0;
return logMapper.saveLog(content);
}
}
程序執(zhí)行結(jié)果:
User 表中成功添加了一條用戶數(shù)據(jù),Log 表執(zhí)行失敗,沒有加入任何數(shù)據(jù),但它并沒有影響到 UserController 中的事務(wù)執(zhí)行。
通過以上結(jié)果可以看出:LogService 中使用的是單獨(dú)的事務(wù),雖然 LogService 中的事務(wù)執(zhí)行失敗了,但并沒有影響 UserController 和 UserService 中的事務(wù)。
NESTED 使用演示
NESTED 是嵌套事務(wù)。
UserController 實(shí)現(xiàn)代碼如下:
"/save")(
public Object save(User user) {
// 插入用戶操作
userService.save(user);
return true;
}
UserService 實(shí)現(xiàn)代碼如下:
propagation = Propagation.NESTED)(
public int save(User user) {
int result = userMapper.save(user);
System.out.println("執(zhí)行 save 方法.");
// 插入日志
logService.saveLog("用戶插入:" + user.getName());
return result;
}
LogService 實(shí)現(xiàn)代碼如下:
propagation = Propagation.NESTED)(
public int saveLog(String content) {
// 出現(xiàn)異常
int i = 10 / 0;
return logMapper.saveLog(content);
}
最終執(zhí)行結(jié)果,用戶表和日志表都沒有添加任何數(shù)據(jù)。
執(zhí)行流程描述:
- UserController 中調(diào)用了 UserService 的添加用戶方法,UserService 使用 NESTED 循環(huán)嵌套事務(wù),并成功執(zhí)行了添加用戶的方法。
- UserService 中調(diào)用了 LogService 的添加方法,LogService 使用了 NESTED 循環(huán)嵌套事務(wù),但在方法執(zhí)行中出現(xiàn)的異常,因此回滾了當(dāng)前事務(wù)。
- 因?yàn)?UserService 使用的是嵌套事務(wù),所以發(fā)生回滾的事務(wù)是全局的,也就是說 UserService 中的添加用戶方法也被回滾了,最終執(zhí)行結(jié)果是用戶表和日志表都沒有添加任何數(shù)據(jù)。
總結(jié)
Spring 事務(wù)傳播機(jī)制是包含多個(gè)事務(wù)的方法在相互調(diào)用時(shí),事務(wù)是如何在這些方法間傳播的。事務(wù)的傳播級(jí)別有 7 個(gè),支持當(dāng)前事務(wù)的:REQUIRED、SUPPORTS、MANDATORY;不支持當(dāng)前事務(wù)的:REQUIRES_NEW、NOT_SUPPORTED、NEVER,以及嵌套事務(wù) NESTED,其中 REQUIRED 是默認(rèn)的事務(wù)傳播級(jí)別。