面試突擊88:加入事務和嵌套事務有什麼區別?
加入事務和嵌套事務是指在 Spring 事務傳播機制中的加入事務(REQUIRED)和嵌套事務(NESTED)的區別,二者看似很像,實則截然不同,那麼它們有什麼區別呢?接下來我們一起來看。
Spring 事務傳播機制是指,包含多個事務的方法在相互調用時,事務是如何在這些方法間傳播的,Spring 事務傳播機制分為 3 大類,總共 7 種級別,如下圖所示:
其中,支援當前事務的 REQUIRED 是加入(當前)事務,而 NESTED 是嵌套(當前)事務,本文要討論的就是這二者的區別。
1.加入事務
加入事務 REQUIRED 是 Spring 事務的默認傳播級別。
所謂的加入當前事務,是指如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。我們這裡重點要討論的是第一種情況,也就是當前存在事務的情況下,它和嵌套事務的區別,接下來我們通過一個示例來看加入事務的使用和執行特點。
我們要實現的是用戶添加功能,只不過在添加用戶時,我們需要給用戶表和日誌表中分別插入一條數據,UserController 實現程式碼如下:
@Transactional(propagation = Propagation.REQUIRED)
@RequestMapping("/add")
public int add(UserInfo userInfo) {
int result = 0;
int userResult = userService.add(userInfo);
System.out.println("用戶添加結果:" + userResult);
if (userResult > 0) {
LogInfo logInfo = new LogInfo();
logInfo.setName("添加用戶");
logInfo.setDesc("添加用戶結果:" + userResult);
int logResult = logService.add(logInfo);
System.out.println("日誌添加結果:" + logResult);
result = 1;
}
return result;
}
從上述程式碼可以看出,添加用戶使用了事務,並設置了事務傳播機製為 REQUIRED(加入事務),此控制器調用的 UserService 實現程式碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public int add(UserInfo userInfo) {
int result = userMapper.add(userInfo);
return result;
}
從上述程式碼可以看出,它也是使用事務,並設置了事務的傳播機製為 REQUIRED,而 LogService 也是類似的實現程式碼:
@Transactional(propagation = Propagation.REQUIRED)
public int add(LogInfo logInfo) {
int result = logMapper.add(logInfo);
try {
int number = 10 / 0;
} catch (Exception e) {
// 手動回滾事務
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
從上述程式碼我們可以看出,在設置事務傳播機制的同時,我們也在程式中主動的設置了一個異常。
運行以上程式的執行結果如下圖所示:
從上述結果我們可以看出:當我們設置了加入事務的事務傳播機制之後,程式的執行結果是將用戶表和日誌表的事務都回滾了。
2.嵌套事務
嵌套事務指的是事務傳播級別中的 NESTED,所謂的嵌套當前事務,是指如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於 REQUIRED。當然,我們本文要研究的重點也是第一種情況,也就是當前存在事務的前提下,嵌套事務和加入事務的區別。
所以接下來我們將上面程式碼中的事務傳播機制改為 NESTED,它的實現程式碼如下。
UserController 實現程式碼如下:
@Transactional(propagation = Propagation.NESTED)
@RequestMapping("/add")
public int add(UserInfo userInfo) {
int result = 0;
int userResult = userService.add(userInfo);
System.out.println("用戶添加結果:" + userResult);
if (userResult > 0) {
LogInfo logInfo = new LogInfo();
logInfo.setName("添加用戶");
logInfo.setDesc("添加用戶結果:" + userResult);
int logResult = logService.add(logInfo);
System.out.println("日誌添加結果:" + logResult);
result = 1;
}
return result;
}
UserService 實現程式碼如下:
@Transactional(propagation = Propagation.NESTED)
public int add(UserInfo userInfo) {
int result = userMapper.add(userInfo);
return result;
}
LogService 實現程式碼如下:
@Transactional(propagation = Propagation.NESTED)
public int add(LogInfo logInfo) {
int result = logMapper.add(logInfo);
try {
int number = 10 / 0;
} catch (Exception e) {
// 手動回滾事務
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
運行以上程式的執行結果如下圖所示:
從上述結果可以看出:當設置嵌套事務的事務傳播級別之後,程式執行了部分事務的回滾,用戶表添加的事務沒有回滾,只是日誌表的事務回滾了。
3.加入事務 VS 嵌套事務
加入事務(REQUIRED)和嵌套事務(NESTED)都是事務傳播機制的兩種傳播級別,如果當前不存在事務,那麼二者的行為是一樣的;但如果當前存在事務,那麼加入事務的事務傳播級別在遇到異常之後,會將事務全部回滾;而嵌套事務在遇到異常時,只是執行了部分事務的回滾。
4.嵌套事務實現原理
事務全部回滾很好理解,這本來就是事務原子性的一種體現,而嵌套事務中的部分事務回滾是怎麼實現的呢?
嵌套事務只所以能實現部分事務的回滾,是因為在資料庫中存在一個保存點(savepoint)的概念,以 MySQL 為例,嵌套事務相當於新建了一個保存點,而滾回時只回滾到當前保存點,因此之前的事務是不受影響的,這一點可以在 MySQL 的官方文檔匯總找到相應的資料://dev.mysql.com/doc/refman/5.7/en/savepoint.html
而 REQUIRED 是加入到當前事務中,並沒有創建事務的保存點,因此出現了回滾就是整個事務回滾,這就是嵌套事務和加入事務的區別。
保存點就像玩通關遊戲時的「遊戲存檔」一樣,如果設置了遊戲存檔,那麼即使當前關卡失敗了,也能繼續上一個存檔點繼續玩,而不是從頭開始玩遊戲。
總結
加入事務(REQUIRED)和嵌套事務(NESTED)都是事務傳播機制中的兩種傳播級別,如果當前不存在事務,那麼二者的行為是一致的;但如果當前存在事務,那麼加入事務的事務傳播級別當遇到異常時會回滾全部事務,而嵌套事務則是回滾部分事務。嵌套事務之所以能回滾部分事務,是因為資料庫中存在一個保存點的概念,嵌套事務相對於新建了一個保存點,如果出現異常了,那麼只需要回滾到保存點即可,這樣就實現了部分事務的回滾。
是非審之於己,毀譽聽之於人,得失安之於數。
公眾號:Java面試真題解析