關鍵機制說明:
1.??事務注解生效??:
@Transactional(rollbackFor = Exception.class)
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean saveUser(UserDTO userDto) {SysUser sysUser = new SysUser();BeanUtils.copyProperties(userDto, sysUser);sysUser.setDelFlag(CommonConstants.STATUS_NORMAL);sysUser.setPassword(ENCODER.encode(userDto.getPassword()));baseMapper.insert(sysUser);List<SysUserRole> userRoleList = userDto.getRole().stream().map(roleId -> {SysUserRole userRole = new SysUserRole();userRole.setUserId(sysUser.getUserId());userRole.setRoleId(roleId);return userRole;}).collect(Collectors.toList());return sysUserRoleService.saveBatch(userRoleList);
}
- 該注解將整個方法納入??同一個數據庫事務??。
rollbackFor = Exception.class
?指定了所有異常(包括?RuntimeException
?和受檢異常)都會觸發回滾。
??2.事務回滾邏輯??:
- 事務內任何操作拋出異常(如數據庫約束沖突、網絡中斷等),整個事務會回滾到方法開始前的狀態。
- 本方法包含兩個數據庫操作:
baseMapper.insert(sysUser)
:插入用戶記錄sysUserRoleService.saveBatch(userRoleList)
:批量插入用戶角色關聯記錄
- ??這兩個操作共享同一個事務??,任何一步失敗都會回滾所有操作。
???3.批量保存的異常行為??:
- MyBatis-Plus 的?
saveBatch()
?在默認配置下,如果批量操作中??任意一條數據失敗??,??會拋出異常??(如?DataAccessException
)。 - 此時事務管理器會捕獲異常,并回滾整個事務。
需要注意的細節:
??批量操作實際執行方式??:
saveBatch()
?默認可能通過 ??分批提交 SQL??(如每批 1000 條)執行。但如果某條數據失敗(如唯一鍵沖突),MyBatis-Plus 會拋出異常,觸發回滾,??已插入的本批次數據也會回滾??。
??自調用問題??:
- 如果該方法被??同一個類中的其他方法直接調用??(而非通過 Spring 代理對象),
@Transactional
?會失效。確保通過 Spring 容器管理的方法調用。
- 如果該方法被??同一個類中的其他方法直接調用??(而非通過 Spring 代理對象),
驗證場景:
// 模擬批量保存時部分數據失敗(如角色ID不存在)
List<SysUserRole> userRoleList = Arrays.asList(
new SysUserRole(sysUser.getUserId(), 1), // 有效數據
new SysUserRole(sysUser.getUserId(), 999) // 無效數據(觸發外鍵沖突)
);sysUserRoleService.saveBatch(userRoleList); // 失敗拋出異常
- ??結果??:
sysUser
?的插入操作也會被回滾,數據庫無新增用戶和角色關聯記錄。
總結:
只要批量保存中有一條記錄失敗拋出異常,整個事務回滾,確保用戶數據和關聯角色數據的一致性??。這是事務 ACID 特性(原子性)的直接體現。
如果將上面的mybatisplus提供的saveBatch批量增加改成自己用for循環單條增加數據,事務還起作用嗎?
答案:
??事務仍然會起作用,而且效果相同!?? 只要循環中的任意一次單條保存失敗,??整個事務都會回滾??(包括?
baseMapper.insert(sysUser)
?插入的用戶記錄和之前成功的角色記錄)。
詳細分析:
1. 修改后的代碼示例(使用循環單條保存):
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean saveUser(UserDTO userDto) {// 插入用戶記錄SysUser sysUser = new SysUser();BeanUtils.copyProperties(userDto, sysUser);sysUser.setDelFlag(CommonConstants.STATUS_NORMAL);sysUser.setPassword(ENCODER.encode(userDto.getPassword()));baseMapper.insert(sysUser);// 循環單條保存用戶角色List<SysUserRole> userRoleList = ...; // 同上for (SysUserRole userRole : userRoleList) {sysUserRoleService.save(userRole); // ? 改為循環單條保存}return true;
}
2. 事務為何依然有效?
關鍵因素 | 說明 |
---|---|
??Spring 事務管理機制?? | @Transactional ?會為整個方法創建一個??數據庫連接級的事務上下文??。 |
??同一事務上下文?? | 循環中的每次?sysUserRoleService.save() ?操作與用戶插入操作共享同一個事務。 |
??異常傳播?? | 循環中任意一次保存失敗拋出異常時,異常會傳播到?@Transactional ?注解層,觸發全局回滾。 |
3. 執行流程(含錯誤場景):
??正確流程??:
用戶插入 → 角色1保存 → 角色2保存 → ... → 全部成功 → ??事務提交????錯誤流程??(假設第3次保存失敗):
用戶插入 → 角色1保存 → 角色2保存 → ? 角色3保存失敗 → 拋出異常 → ??事務回滾??
→ ??已插入的用戶和角色1、2記錄均被撤銷??
重要注意事項:
1. ??性能陷阱??
?? 將?saveBatch()
?批量操作改為循環單條保存會??嚴重降低性能??:
- ??N+1 問題??:每條數據單獨執行一次 SQL(產生 N 次網絡IO + SQL 解析開銷)
- ??對比??:
saveBatch()
?默認會合并為單條 SQL 或小批量提交(如?INSERT INTO table VALUES (...), (...), ...
)
2. ??異常處理建議??
避免在循環內捕獲異常后繼續執行(除非明確需要部分提交):
// ? 錯誤做法(導致事務失效):
for (SysUserRole userRole : userRoleList) {
try {
sysUserRoleService.save(userRole);
} catch (Exception e) {
// 捕獲后不拋出,事務無法感知異常,繼續提交后續數據!
}
}
3. ??嵌套事務風險??
如果?sysUserRoleService.save()
?也有?@Transactional
:
- 默認傳播行為 (
REQUIRED
) 會加入當前事務 → ??安全,行為一致?? - 若改為?
REQUIRES_NEW
?則每次循環新建獨立事務 → ??破壞原子性(部分提交)??
結論:
- ??事務有效??:循環單條保存不會破壞事務的原子性,失敗時仍會全局回滾。
- ??避免濫用循環??:務必優先使用批量操作(如?
saveBatch()
)以保證性能。 - ??統一事務上下文??:只要不修改默認的傳播行為,嵌套調用的操作仍在同一事務中。
?
?
?