平时大家在写代码的时候避免不了要用到@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事务的控制。