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

关键机制说明:

1.​​事务注解生效​​:

@Transactional(rollbackFor = Exception.class)

java 复制代码
@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. 修改后的代码示例(使用循环单条保存):
java 复制代码
@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. ​统一事务上下文​:只要不修改默认的传播行为,嵌套调用的操作仍在同一事务中。
复制代码
相关推荐
IT研究室3 分钟前
最新计算机毕业设计选题推荐-基于spring boot的心桥·心理健康综合服务平台-网站-文档指导-Java-springboot
java·spring boot·课程设计
Sarvartha28 分钟前
Object 类
java·开发语言
知守观30 分钟前
feign-core 就在依赖树里,运行时却找不到类:一次 provided scope 引发的启动失败排查
java·spring cloud·maven
她的男孩31 分钟前
企业接口照样拦得住:独立 Flyway、@RequiresFeature 与离线许可证
java·spring boot·后端
十年Java程序媛32 分钟前
Lambda 与函数式接口|别只会复制 ()->{},底层规则和坑一次性讲清
java·spring boot·后端
Nozokime39 分钟前
ThreadLocal 在流式 Agent 里静默失效:LangChain4j 轨迹埋点的零侵入方案
java
小宋102144 分钟前
OpenTelemetry GenAI可观测性实战:串起模型、工具、Token与错误
java·人工智能·算法·贪心算法
m0_587383001 小时前
工业场景设备维修维护实战技巧 全流程标准化落地与常见问题排查指南
java·spring·小程序·架构·需求分析
郑州光合科技余经理1 小时前
本地生活平台搭建:跨业态用户标识怎么贯通
java·开发语言·前端·后端·uni-app·php·ai编程
木头科技2 小时前
【AI 工程化第八篇】Spring AI 多模型路由实战:DeepSeek、通义、Kimi、OpenAI 如何统一接入和自动切换
java·人工智能·spring