@Transactional失效的场景都有哪些呢 ?

一、代理不生效导致


1、同一个类中的方法,通过this调用导致失效

同一个类中,addOrder()方法无事务,addOrder2()方法存在事务,addOrder()调用addOrder2()

我们通过外部方法调用addOrder()方法,来完成数据库的插入,通过手动的设置异常order/0,来观察addOrder2()方法中的数据是否会正常回滚。

通过数据库的结果显示,数据正常入库了,证明了我们的事务并未生效。


同一个类中,addOrder()和addOrder2()都存在事务,addOrder()调用addOrder2()。

order/0 产生异常之后,通过数据库的结果显示,发现数据并未入库,说明事务生效了。


我们发现并不是所有同一个类,方法的内部调用事务都会失效。


那我们就来了解一下事务为啥不生效 ?

看图 !外部代码调用addOrder()方法时,并没有直接进入目标方法,而是首先进入了DynamicAdvisedInterceptor的intercept()方法中。

这是因为我们的orderService是一个代理对象,这里调用addOrder()方法的,其实是代理对象,而代理对象对目标方法的调用,

会转发进入CGLIB动态代理类的intercept()方法中进行增强。

然而当调用this.addOrder2()方法,而这里this是原生对象,并不是代理,自然就没有事务控制了


解决:同一个类中的方法,通过this调用导致失效


修正方式:

  • ①:将this换成代理的userService, 可以自己注入自己

       @Resource private 
       OrderService orderService,
       当然也可以不用注入,直接在Spring容器中获取userService这个bean 
    
  • ②:将#addOrder()方法开启事务即加上@Transactional(rollbackFor = Exception.class)


2、事务方法被final、static修饰

失效原因:CGLIB是通过生成目标类的子类方式生成代理类的,被final、static修饰的方法,无法被子类重写


3、@Transactional 应用在非 public 修饰的方法上

@Transactional(rollbackFor = Exception.class)
private void addUserRole(Long userId, List<Long> roleIds) {
    if (CollectionUtils.isEmpty(roleIds)) {
        return;
    }
    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("发生异常咯");
}

idea也会提示爆红:

Spring通过CGLIB动态代理来增强生产代理对象,CGLIB 通过继承方式实现代理类,private 方法在子类不可见,自然也就无法进行事务增强。

事务在基于AOP事务控制实现原理一文中也分析过,会调用到AbstractFallbackTransactionAttributeSource的computeTransactionAttribute()方法

   @Nullable
   protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
      // Don't allow no-public methods as required.
      if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
       return null;
      }
      
      ......
   }


4、异常被方法内部try catch捕获,没有重新抛出

当外部接口调用addOrder()方法,异常发生的时候,数据库数据正常入库了,事务未生效。

在TransactionAspectSupport.invokeWithinTransaction()方法中,我们可以看到以下逻辑。


5、嵌套事务回滚多了


示例代码:


6、rollbackFor属性设置错误


示例代码:

@Service
public class OrderServiceImpl {
    @Autowired
    private OrderMapper orderMapper;

    @Transactional
    public int addOrder(Double amount, String address) throws FileNotFoundException {
        int order = orderMapper.addOrder(amount, address);
        throw new FileNotFoundException("11111");
    }
}

在demo中,我们手动的抛出FileNotFountExceptionshou异常(受检异常。),这是一个IOException异常。


当我们使用@Transactional,在未对rollbackFor做配置的情况下,默认是支持对Runtime和Error异常的回滚的。

但当我们的demo中的异常是IOException的时候

从源码694行else逻辑的注释上我们也能看出,无法回滚异常。

所以通常情况下,我们建议指定@Transactional(rollbackFor = Exception.class)的方式进行异常捕获。


7、设置不支持事务的传播机制


Spring支持了7种传播机制,分别为:

上面不支持事务的传播机制为:propagation_supports、propagation_not_supported、propagation_never


如果配置了这三种传播方式的话,在发生异常的时候,事务是不会回滚的。


示例代码:

  @Service
  public class OrderServiceImpl {
      @Autowired
      private OrderMapper orderMapper;

      @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
      public int addOrder(Double amount, String address) throws FileNotFoundException {
          int order = orderMapper.addOrder(amount, address);
          throw new FileNotFoundException("11111");
      }
  }
posted @ 2024-12-26 22:01  jock_javaEE  阅读(79)  评论(0)    收藏  举报