论Spring事务失效的多种场景

一、前言
在实际开发当中,如果使用事务不当,会造成Spring事务的失效,那么可能就会引发一些问题,如何解决呢,首先需要了解导致Spring事务失效的场景。

二、Spring事务失效的场景
1.不正确的捕获异常导致事务失效。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MyFirstService {
    @Autowired
    private ServiceMapper serviceMapper;

    @Transactional
    public void addService(MyService service){
        try{
            serviceMapper.insert(service);
            updateData(service);
        }catch (Exception e){
            System.err.println(e.getMessage());
        }

    }
    public void updateData(MyService service){
        serviceMapper.updateByServiceId(service);
    }
}

这种捕获了异常却并未抛出会导致事务失效。
解法1:异常原样抛出
在 catch 块添加 throw new RuntimeException(e);
解法2:手动设置 TransactionStatus.setRollbackOnly()
在 catch 块添加 TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

2.事务方法访问权限问题导致事务失效。

 @Transactional
    protected void addService(MyService service){
        try{
            serviceMapper.insert(service);
            updateData(service);
        }catch (Exception e){
            System.err.println(e.getMessage());
        }

    }
    public void updateData(MyService service){
        serviceMapper.updateByServiceId(service);
    }

上面定义addService方法的访问权限为protected ,这样最终会导致事务失效,spring要求被代理方法必须是public的。
想要了解代理方法必须是public的,我们需要看spring事务的源码,在
AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。
解法:事务方法访问权限需定义为public。

3.方法用final修饰。

@Transactional
    public final void addService(MyService service){
        try{
            serviceMapper.insert(service);
            updateData(service);
        }catch (Exception e){
            System.err.println(e.getMessage());
        }

    }
    public void updateData(MyService service){
        serviceMapper.updateByServiceId(service);
    }

我们可以看到add方法被定义成了final的,这样会导致事务失效。
看过spring事务的源码,就会知道spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。
但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而无法添加事务功能。
解法:事务方法不能定义为final。

4.方法内部调用
有时候我们需要在某个Service类的某个方法中,调用另外一个事务方法,例如:

@Service
public class MyFirstService {
    @Autowired
    private ServiceMapper serviceMapper;


    public  void addService(MyService service){
        try{
            updateData(service);
        }catch (Exception e){
            System.err.println(e.getMessage());
        }

    }
    @Transactional
    public void updateData(MyService service){
        serviceMapper.insert(service);
        serviceMapper.updateByServiceId(service);
    }
}

我们看到在方法addService中,直接调用事务方法updateData。从前面的内容可以知道,updateData方法拥有事务的能力是因为spring aop生成代理了对象,但是这种方法直接调用了this对象的方法,所以updateData方法不会生成事务。
由此可见,在同一个类中的方法直接内部调用,会导致事务失效。

解法1: 新加一个Service方法
这个方法非常简单,、需要新加一个Service方法,把@Transactional注解加到新Service方法上,把需要事务执行的代码移到新方法中。

@Service
public class MyFirstService {
    @Autowired
    private MyFirstServiceB myFirstServiceB ;

    public  void addService(MyService service){
        try{
            myFirstServiceB.updateData(service);
        }catch (Exception e){
            System.err.println(e.getMessage());
        }

    }
}
@Service
public class MyFirstServiceB {
    @Autowired
    private ServiceMapper serviceMapper;
    
   @Transactional
    public void updateData(MyService service){
        serviceMapper.insert(service);
        serviceMapper.updateByServiceId(service);
    }
}

解法2:在该Service类中注入自己

@Service
public class MyFirstService {
	@Autowired
    private MyFirstService myFirstService ;
    @Autowired
    private ServiceMapper serviceMapper;


    public  void addService(MyService service){
        try{
           myFirstService.updateData(service);
        }catch (Exception e){
            System.err.println(e.getMessage());
        }

    }
    @Transactional
    public void updateData(MyService service){
        serviceMapper.insert(service);
        serviceMapper.updateByServiceId(service);
    }
}

5.多线程调用
在实际项目开发中,多线程的使用场景还是挺多的。如果spring事务用在多线程场景中,那么就会存在一些问题。

@Service
public class MyFirstService {
    @Autowired
    private ServiceMapper serviceMapper;

    @Transactional
    public  void addService(MyService service){
        try{
            serviceMapper.insert(service);
            new Thread(()->{
                updateData(service);
            }).start();
        }catch (Exception e){
            System.err.println(e.getMessage());
        }

    }

    public void updateData(MyService service){

        serviceMapper.updateByServiceId(service);
    }
}

我们可以看到事务方法addService中,调用了updateData,但是方法updateData是在另外一个线程中调用的。
这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想updateData方法中抛了异常,addService方法也回滚是不可能的。

6.手动抛了别的异常
即使开发者没有手动捕获异常,但如果抛的异常不正确,spring事务也不会回滚。

@Transactional
    public  void addService(MyService service){
        try{
            serviceMapper.insert(service);

            updateData(service);
            
        }catch (Exception e){
            throw new Exception("失败");
        }

    }

    public void updateData(MyService service){

        serviceMapper.updateByServiceId(service);
    }

这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:@Transactional(rollbackFor = Exception.class), 这个配置仅限于 Throwable 异常类及其子类。

以上就是其中比较常见的导致事务失效的一些场景,当然还有一些其他的,这里就不多介绍了,大家有兴趣可以去了解下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜空下的星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值