加入事務(wù)和嵌套事務(wù)有什么區(qū)別?你明白了嗎?
加入事務(wù)和嵌套事務(wù)是指在 Spring 事務(wù)傳播機(jī)制中的加入事務(wù)(REQUIRED)和嵌套事務(wù)(NESTED)的區(qū)別,二者看似很像,實(shí)則截然不同,那么它們有什么區(qū)別呢?接下來我們一起來看。
Spring 事務(wù)傳播機(jī)制是指,包含多個(gè)事務(wù)的方法在相互調(diào)用時(shí),事務(wù)是如何在這些方法間傳播的,Spring 事務(wù)傳播機(jī)制分為 3 大類,總共 7 種級別,如下圖所示:
其中,支持當(dāng)前事務(wù)的 REQUIRED 是加入(當(dāng)前)事務(wù),而 NESTED 是嵌套(當(dāng)前)事務(wù),本文要討論的就是這二者的區(qū)別。
1.加入事務(wù)
加入事務(wù) REQUIRED 是 Spring 事務(wù)的默認(rèn)傳播級別。
所謂的加入當(dāng)前事務(wù),是指如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則創(chuàng)建一個(gè)新的事務(wù)。我們這里重點(diǎn)要討論的是第一種情況,也就是當(dāng)前存在事務(wù)的情況下,它和嵌套事務(wù)的區(qū)別,接下來我們通過一個(gè)示例來看加入事務(wù)的使用和執(zhí)行特點(diǎn)。
我們要實(shí)現(xiàn)的是用戶添加功能,只不過在添加用戶時(shí),我們需要給用戶表和日志表中分別插入一條數(shù)據(jù),UserController 實(shí)現(xiàn)代碼如下:
@Transactional(propagation = Propagation.REQUIRED)
@RequestMapping("/add")
public int add(UserInfo userInfo) {
int result = 0;
int userResult = userService.add(userInfo);
System.out.println("用戶添加結(jié)果:" + userResult);
if (userResult > 0) {
LogInfo logInfo = new LogInfo();
logInfo.setName("添加用戶");
logInfo.setDesc("添加用戶結(jié)果:" + userResult);
int logResult = logService.add(logInfo);
System.out.println("日志添加結(jié)果:" + logResult);
result = 1;
}
return result;
}
從上述代碼可以看出,添加用戶使用了事務(wù),并設(shè)置了事務(wù)傳播機(jī)制為 REQUIRED(加入事務(wù)),此控制器調(diào)用的 UserService 實(shí)現(xiàn)代碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public int add(UserInfo userInfo) {
int result = userMapper.add(userInfo);
return result;
}
從上述代碼可以看出,它也是使用事務(wù),并設(shè)置了事務(wù)的傳播機(jī)制為 REQUIRED,而 LogService 也是類似的實(shí)現(xiàn)代碼:
@Transactional(propagation = Propagation.REQUIRED)
public int add(LogInfo logInfo) {
int result = logMapper.add(logInfo);
try {
int number = 10 / 0;
} catch (Exception e) {
// 手動(dòng)回滾事務(wù)
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
從上述代碼我們可以看出,在設(shè)置事務(wù)傳播機(jī)制的同時(shí),我們也在程序中主動(dòng)的設(shè)置了一個(gè)異常。
運(yùn)行以上程序的執(zhí)行結(jié)果如下圖所示:
從上述結(jié)果我們可以看出:當(dāng)我們設(shè)置了加入事務(wù)的事務(wù)傳播機(jī)制之后,程序的執(zhí)行結(jié)果是將用戶表和日志表的事務(wù)都回滾了。
2.嵌套事務(wù)
嵌套事務(wù)指的是事務(wù)傳播級別中的 NESTED,所謂的嵌套當(dāng)前事務(wù),是指如果當(dāng)前存在事務(wù),則創(chuàng)建一個(gè)事務(wù)作為當(dāng)前事務(wù)的嵌套事務(wù)來運(yùn)行;如果當(dāng)前沒有事務(wù),則該取值等價(jià)于 REQUIRED。當(dāng)然,我們本文要研究的重點(diǎn)也是第一種情況,也就是當(dāng)前存在事務(wù)的前提下,嵌套事務(wù)和加入事務(wù)的區(qū)別。
所以接下來我們將上面代碼中的事務(wù)傳播機(jī)制改為 NESTED,它的實(shí)現(xiàn)代碼如下。
UserController 實(shí)現(xiàn)代碼如下:
@Transactional(propagation = Propagation.NESTED)
@RequestMapping("/add")
public int add(UserInfo userInfo) {
int result = 0;
int userResult = userService.add(userInfo);
System.out.println("用戶添加結(jié)果:" + userResult);
if (userResult > 0) {
LogInfo logInfo = new LogInfo();
logInfo.setName("添加用戶");
logInfo.setDesc("添加用戶結(jié)果:" + userResult);
int logResult = logService.add(logInfo);
System.out.println("日志添加結(jié)果:" + logResult);
result = 1;
}
return result;
}
UserService 實(shí)現(xiàn)代碼如下:
@Transactional(propagation = Propagation.NESTED)
public int add(UserInfo userInfo) {
int result = userMapper.add(userInfo);
return result;
}
LogService 實(shí)現(xiàn)代碼如下:
@Transactional(propagation = Propagation.NESTED)
public int add(LogInfo logInfo) {
int result = logMapper.add(logInfo);
try {
int number = 10 / 0;
} catch (Exception e) {
// 手動(dòng)回滾事務(wù)
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
運(yùn)行以上程序的執(zhí)行結(jié)果如下圖所示:
從上述結(jié)果可以看出:當(dāng)設(shè)置嵌套事務(wù)的事務(wù)傳播級別之后,程序執(zhí)行了部分事務(wù)的回滾,用戶表添加的事務(wù)沒有回滾,只是日志表的事務(wù)回滾了。
3.加入事務(wù) VS 嵌套事務(wù)
加入事務(wù)(REQUIRED)和嵌套事務(wù)(NESTED)都是事務(wù)傳播機(jī)制的兩種傳播級別,如果當(dāng)前不存在事務(wù),那么二者的行為是一樣的;但如果當(dāng)前存在事務(wù),那么加入事務(wù)的事務(wù)傳播級別在遇到異常之后,會(huì)將事務(wù)全部回滾;而嵌套事務(wù)在遇到異常時(shí),只是執(zhí)行了部分事務(wù)的回滾。
4.嵌套事務(wù)實(shí)現(xiàn)原理
事務(wù)全部回滾很好理解,這本來就是事務(wù)原子性的一種體現(xiàn),而嵌套事務(wù)中的部分事務(wù)回滾是怎么實(shí)現(xiàn)的呢?
嵌套事務(wù)只所以能實(shí)現(xiàn)部分事務(wù)的回滾,是因?yàn)樵跀?shù)據(jù)庫中存在一個(gè)保存點(diǎn)(savepoint)的概念,以 MySQL 為例,嵌套事務(wù)相當(dāng)于新建了一個(gè)保存點(diǎn),而滾回時(shí)只回滾到當(dāng)前保存點(diǎn),因此之前的事務(wù)是不受影響的,這一點(diǎn)可以在 MySQL 的官方文檔匯總找到相應(yīng)的資料:https://dev.mysql.com/doc/refman/5.7/en/savepoint.html
而 REQUIRED 是加入到當(dāng)前事務(wù)中,并沒有創(chuàng)建事務(wù)的保存點(diǎn),因此出現(xiàn)了回滾就是整個(gè)事務(wù)回滾,這就是嵌套事務(wù)和加入事務(wù)的區(qū)別。
保存點(diǎn)就像玩通關(guān)游戲時(shí)的“游戲存檔”一樣,如果設(shè)置了游戲存檔,那么即使當(dāng)前關(guān)卡失敗了,也能繼續(xù)上一個(gè)存檔點(diǎn)繼續(xù)玩,而不是從頭開始玩游戲。
總結(jié)
加入事務(wù)(REQUIRED)和嵌套事務(wù)(NESTED)都是事務(wù)傳播機(jī)制中的兩種傳播級別,如果當(dāng)前不存在事務(wù),那么二者的行為是一致的;但如果當(dāng)前存在事務(wù),那么加入事務(wù)的事務(wù)傳播級別當(dāng)遇到異常時(shí)會(huì)回滾全部事務(wù),而嵌套事務(wù)則是回滾部分事務(wù)。嵌套事務(wù)之所以能回滾部分事務(wù),是因?yàn)閿?shù)據(jù)庫中存在一個(gè)保存點(diǎn)的概念,嵌套事務(wù)相對于新建了一個(gè)保存點(diǎn),如果出現(xiàn)異常了,那么只需要回滾到保存點(diǎn)即可,這樣就實(shí)現(xiàn)了部分事務(wù)的回滾。