Spring Boot项目业务代码中使用@Transactional事务失效踩坑点总结( 四 )

你会发现只会同时插入失败,无法实现上面所说的 。这时候你可能会想到,既然addUserRole()抛出了异常不能插入用户角色,但是addUser()不想受影响,正常添加用户,那么何不在addUser()里面对userRoleService.addUserRole()进行异常捕获,不就可以解决问题了吗?真是如此吗,就让我们来验证一下:
@Transactional(rollbackFor = Exception.class)public void addUser(UserParam param) {User user = PtcBeanUtils.copy(param, User.class);// 添加用户userDAO.insert(user);// 添加用户角色try {userRoleService.addUserRole(user.getId(), param.getRoleIds());} catch (Exception e) {log.error(e.getMessage());}}执行会发现,用户同样没有添加成功,看日志报错:
[1689568520410750976] [ERROR] [2023-08-10 17:25:02.023] [http-nio-18888-exec-1@56682]com.plasticene.fast.service.impl.UserServiceImpl addUser : 发生异常咯[1689568520410750976] [ERROR] [2023-08-10 17:25:02.097] [http-nio-18888-exec-1@56682]com.plasticene.boot.web.core.global.GlobalExceptionHandler exceptionHandler : 【系统异常】org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.JAVA:870)可以看到发生异常咯是我们在addUser()中捕获到输出的,但是紧接着下一行发现有报出一个异常UnexpectedRollbackException 。
原因是,主方法添加用户的逻辑和子方法添加用户角色的逻辑是同一个事务,子逻辑标记了事务需要回滚,主逻辑自然也不能提交了 。
修正方式:其实要想新增用户角色失败不影响添加用户,只需要让新增用户角色单独开启一个新事务即可 。
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)public void addUserRole(Long userId, List<Long> roleIds) {List<UserRole> userRoles = new ArrayList<>();roleIds.forEach(roleId -> {UserRole userRole = new UserRole();userRole.setUserId(userId);userRole.setRoleId(roleId);userRoles.add(userRole);});userRoleDAO.insertBatch(userRoles);throw new RuntimeException("发生异常啦!");}3.6 @Transactional长事务导致生产事故很多开发都觉得Spring的声明式事务使用非常简单,即@Transactional,所以从来不注重细节 。当 Spring 遇到该注解时,会自动从数据库连接池中获取 connection,并开启事务然后绑定到 ThreadLocal 上,对于@Transactional注解包裹的整个方法都是使用同一个connection连接 。如果我们出现了耗时的操作,比如第三方接口调用、业务逻辑复杂、大批量数据处理等就会导致我们我们占用这个connection的时间会很长,数据库连接一直被占用不释放 。一旦类似操作过多,就会导致数据库连接池耗尽 。这就是典型的长事务问题
长事务引发的常见危害有:

  1. 数据库连接池被占满,应用无法获取连接资源;
  2. 容易引发数据库死锁;
  3. 数据库回滚时间长;
  4. 在主从架构中会导致主从延时变大 。
服务系统开始出现故障:数据库监控平台一直收到告警短信,数据库连接不足,出现大量死锁;日志显示调用流程引擎接口出现大量超时;同时一直提示CannotGetJdbcConnectionException,数据库连接池连接占满 。
要想解决这个问题其实也不难,只需要对方法进行拆分,将不需要事务管理的逻辑与事务操作分开,这样就可以有效控制事务的时长从而避免长事务 。当然对一个方法逻辑拆分成多个子方法很有可能造成上面叙述的事务不生效的情况,不过我相信你看到上面的总结肯定没问题啦 。
4.总结Spring的声明式事务使用@Transactional注解在开发时确实很方便,但是稍有不慎使用不当就会导致事务失效数据不一致、甚至是系统数据库性能问题 。所以上面满满的干货总结都是出自日常工作中碰到的,有效帮你避坑 。
本文转载自微信公众号「Shepherd进阶笔记」




推荐阅读