平时大家在写代码的时候避免不了要用到@Transaction这个注解来使业务保持一致性,但是有的小伙伴可能会发现,明明写了@Transaction但是Spring事务失效了,业务并没有按我们想的那样去保持一致性。因此本文写了几种目前作者知道的几种原因

①方法异常没有抛出去

当业务发生异常的时候,会向外抛,也就是当Spring感知到异常的时候才会进行事务的回滚,但是如果我们在方法内就将异常截获了,那么Spring无法感知到异常,就无法进行事务的回滚,举个例子:

@Transaction
public void test(){
        try {
            System.out.println("Spring事务"); //正常代码执行
            int a=1/0;  //异常代码  被捕获
        }catch (Exception e){
            System.out.println("出现异常");//这里并没有抛出异常,而是自己处理了 因此Spring无法感知
        }
}

②方法异常不属于Spring事务默认处理的异常

Spring默认只会处理RuntimeException和Error这两种,因此如果是其他异常的话,默认是不会处理的,如果想要Spring处理的话,需要对@Transaction注解进行处理:@Transaciton({异常的Class})才可以处理。

③不满足开启Spring事务的两个条件

首先,我们需要知道Spring开启事务功能的条件是:由动态代理对象来调用带有@Transaction注解的方法。因此,从这句话我们可以明白Spring开启事务的两个条件为:①要是动态对象来调用②带有@Transaction的方法。

这里帮大家踩一个坑:大家在写代码的时候,可能会遇到无@Transaction注解的方法去调用有@Transaction注解的方法,这个时候大家很容易踩坑,这里我们先上代码,举个例子:

public class temp {
    @Autowired
    MediaFileService mediaFileService;

    public void NoAnnotation(){
        System.out.println("没注解的方法"); //执行业务
        HaveAnnotation();//调用有注解的方法
    }

    @Transactional
    public void HaveAnnotation(){
        System.out.println("有注解的方法");//执行业务
    }
}

比如上文中,NoAnnotation()是没有事务注解的,方法体内调用了有事务注解的HaveAnnotation(),这里大家去设个断点会发现,这里调用HaveAnnotation()并不是由代理对象来调用的,而是由this对象来调用的,因此之类不满足我们上面说的两个条件,便会导致事务失效。

因此,这里的解决的方法是:在代码中注入自己,由自己的代理对象来调用HaveAnnotation()就可以了。如下:

@Configuration
public class temp {
    @Autowired
    MediaFileService mediaFileService;
    
    @Autowired
    temp proxy; //注入自己
    public void NoAnnotation(){
        System.out.println("没注解的方法"); //执行业务
        proxy.HaveAnnotation();//调用有注解的方法
    }

    @Transactional
    public void HaveAnnotation(){
        System.out.println("有注解的方法");//执行业务
    }
}

这样由proxy(自己的代理对象)来调用就满足上述两点条件,开启Spring事务了。

④滥用@Transaction,不注意使用范围

@Transaction的使用范围是:①类上。②接口上。③public方法上。很多小伙伴可能会在非public的方法上加上@Transaction,因此会导致事务失效,这也是一个坑。

⑤数据库的引擎不支持事务

如果使用的数据库引擎不支持事务(如MySQL中的MyISAM引擎)那么也无法正常使用事务。

⑥方法的传播类型不支持事务

如果内部方法的事务传播类型为不支持事务的传播类型,则内部方法的事务在Spring中会失效。

例如,如下代码所示:

@Service
public class OrderService {
 @Autowired
 private OrderDao orderDao;
 @Autowired
 private ProductDao productDao;
 
 @Transactional(propagation = Propagation.REQUIRED)
 public void submitOrder(){
  //生成订单
  Order order = new Order();
  long number = Math.abs(new Random().nextInt(500));
  order.setId(number);
  order.setOrderNo("order_" + number);
  orderDao.saveOrder(order);
  //减库存
  this.updateProductStockCountById(1, 1L);
 }
 
 @Transactional(propagation = Propagation.NOT_SUPPORTED)
 public void updateProductStockCountById(Integer stockCount, Long id){
  productDao.updateProductStockCountById(stockCount, id);
 }
}

由于updateProductStockCountById()方法的事务传播类型为NOT_SUPPORTED,不支持事务,则updateProductStockCountById()方法的事务会在Spring中失效。

⑦业务代码不在同一线程

Spring事务实现中使用了ThreadLocal,ThreadLocal可以实现同一个线程中数据共享,必须是同一个线程的时候,数据才可以共享,这就要求业务代码必须和Spring事务的源码执行过程必须在一个线程中,才会受Spring事务的控制。