@Transactional事务注解的批量回滚机制

发布于:2025-07-05 ⋅ 阅读:(24) ⋅ 点赞:(0)

关键机制说明:

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 容器管理的方法调用。

验证场景:

// 模拟批量保存时部分数据失败(如角色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. ​正确流程​​:
    用户插入 → 角色1保存 → 角色2保存 → ... → 全部成功 → ​​事务提交​

  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 则每次循环新建独立事务 → ​​破坏原子性(部分提交)​

结论:

  1. ​事务有效​​:循环单条保存不会破坏事务的原子性,失败时仍会全局回滚。
  2. ​避免滥用循环​​:务必优先使用批量操作(如 saveBatch())以保证性能。
  3. ​统一事务上下文​​:只要不修改默认的传播行为,嵌套调用的操作仍在同一事务中。

 

 


 


网站公告

今日签到

点亮在社区的每一天
去签到