Spring声明式事务在service内部之间调用失效问题

最近在开发过程中遇到了一个问题,当在Controller中调用Service中A()方法,A方法内部又调用Service中B()方法,由于A方法中只有查询操作所以没有加事务控制,B方法中含有多次修改操作所以增加了@Transactional注解,结果在A方法调用完B方法后,程序报错了,但是B方法中修改操作的数据竟然成功了,我擦~什么鬼,于是开启了探索Spring事务之路,直接上示例。

示例1:A方法无事务,B方法加事务

@RestController
public class Controller{
    
    @Autowired
    private StudentcardService studentcardService;  
  
    @RequestMapping(value = "/test/{id}}", method = RequestMethod.GET)
    public Response queryStudentCard(@PathVariable("id") String id) {
       studentcardService.updateA(id);
    }
}
@Service
public class StudentcardServiceImpl implements StudentcardService {

    @Resource
    private StudentCardMapper studentCardMapper;

    @Override
    public void updateA(String id) {
        //先去调用内部方法B
        this.updateB(id);
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改问题字段
        sc.setQuestion("AAAAA");
        studentCardMapper.update(sc);
    }

    @Override
    @Transactional
    public void updateB(String id) {
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改答案字段
        sc.setAnswer("BBBBB");
        studentCardMapper.update(sc);
        //修改完数据后报错
        double i=1/0;
    }
}

访问后执行结果如下:
在这里插入图片描述
然而事务并没有起作用~接着进行测试
示例2:将A方法加事务,B方法不加事务

@Service
public class StudentcardServiceImpl implements StudentcardService {

    @Resource
    private StudentCardMapper studentCardMapper;

    @Override
    @Transactional
    public void updateA(String id) {
        //先去调用内部方法B
        this.updateB(id);
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改问题字段
        sc.setQuestion("AAAAA");
        studentCardMapper.update(sc);
    }

    @Override
    public void updateB(String id) {
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改答案字段
        sc.setAnswer("BBBBB");
        studentCardMapper.update(sc);
        //修改完数据后报错
        double i=1/0;
    }
}

访问后执行结果如下:
在这里插入图片描述
事务起作用了,都没有修改成功!接下来我们来个增强版,加上try后看下会有怎样的效果

示例3:A方法加事务,B方法没有事务,但是在A调用B方法时用try进行包裹,B方法中有错误

@Service
public class StudentcardServiceImpl implements StudentcardService {

    @Resource
    private StudentCardMapper studentCardMapper;

    @Override
    @Transactional
    public void updateA(String id) {
        //先去调用内部方法B
        try {
            this.updateB(id);
        }catch (Exception e){}
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改问题字段
        sc.setQuestion("AAAAA");
        studentCardMapper.update(sc);
    }

    @Override
    public void updateB(String id) {
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改答案字段
        sc.setAnswer("BBBBB");
        studentCardMapper.update(sc);
        //修改完数据后报错
        double i=1/0;
    }
}

访问后执行结果如下:
在这里插入图片描述
由于报错被try包起来了,所以数据都插入了!那如果将报错信息放到执行完方法B后呢,会怎样呢?

示例4:A方法加事务,B方法没有事务,但是在A调用B方法时用try进行包裹,A方法中有错误

@Service
public class StudentcardServiceImpl implements StudentcardService {

    @Resource
    private StudentCardMapper studentCardMapper;

    @Override
    @Transactional
    public void updateA(String id) {
        //先去调用内部方法B
        try {
            this.updateB(id);
        }catch (Exception e){}
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改问题字段
        sc.setQuestion("AAAAA");
        studentCardMapper.update(sc);
        //修改完数据后报错
        double i=1/0;
    }

    @Override
    public void updateB(String id) {
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改答案字段
        sc.setAnswer("BBBBB");
        studentCardMapper.update(sc);
    }
}

访问后执行结果如下:
在这里插入图片描述
哇塞,数据都没有插入呢!这是因为在事务提交前报错了,事务全部rollback了,下面言归正传,示例1为何不能成功呢?于是查询各种资料终于找到了缘由,并对示例1进行改造

示例5:A方法无事务,B方法加事务

@Service
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
public class StudentcardServiceImpl implements StudentcardService {

    @Resource
    private StudentCardMapper studentCardMapper;

    @Override
    public void updateA(String id) {
        //先去调用内部方法B
        StudentcardService studentcardService = (StudentcardService) AopContext.currentProxy();
        studentcardService.updateB(id);
        //this.updateB(id);
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改问题字段
        sc.setQuestion("AAAAA");
        studentCardMapper.update(sc);
    }

    @Override
    @Transactional
    public void updateB(String id) {
        StudentCard sc =new StudentCard();
        sc.setScId(id);
        //修改答案字段
        sc.setAnswer("BBBBB");
        studentCardMapper.update(sc);
        //修改完数据后报错
        double i=1/0;
    }
}

访问后执行结果如下:
在这里插入图片描述
哈哈,数据没有进行修改,事务起作用了!

下面说下具体对原因:
示例1 事务没有起作用,是由于Spring事务本质是基于AOP代理来实现的,当Controller调用Service的方法A是基于proxy的,所以会切入,但是方法A在调用方法B时,属于类内部调用,即使方法B上加上了@Transactional注解,但没有Spring代理了,所以不受事务控制,自然事务不会生效。
在这里插入图片描述
在这里插入图片描述
示例2 事务可以起作用是由于事务的传播行为导致的,默认事务的传播行为为:PROPAGATION_REQUIRED 。方法A标注了注解@Transactional ,执行的时候传播给方法B,因为方法A开启了事务,线程内的connection的属性autoCommit=false,并且执行到方法B时,事务传播依然是生效的,得到的还是方法A的connection,autoCommit还是为false,所以事务生效;反之,如果方法A没有注解@Transactional 时,是不受事务管理的,autoCommit=true,那么传播给方法B的也为true,执行完自动提交,即使B标注了@Transactional 事务也是不起作用的。
示例5事务又可以起作用的,是由于我们在方法A调用方法B时,先获取到了Service的当前代理,然后用当前代理去调用方法B,所以事务当然会生效了~

顺便补充下事务的传播行为,事务的传播行为是为了解决业务层方法之间相互调用,产生的事务应该如何进行传递的问题。spring有如下7种传播行为:

1、PROPAGATION_REQUIRED:支持当前事务,如果当前不存在事务则新建一个。

2、PROPAGATION_SUPPORTS:支持当前事务,如果不存在,就不使用事务。

3、PROPAGATION_MANDATORY:支持当前事务,如果不存在,则抛出异常。

4、PROPAGATION_REQUIRES_NEW:如果当前有事务存在,挂起当前事务,创建一个新的事务。

5、PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前有事务存在,挂起当前事务。

6、PROPAGATION_NEVER:以非事务方式运行,如果当前有事务存在,抛出异常。

7、PROPAGATION_NESTED:如果当前存在一个事务,则该方法运行在一个嵌套的事务中。被嵌套的事务可以从当前事务中单独的提交和回滚。如果当前不存在事务,则开始一个新的事务。各厂商对这种传播行为的支持参差不齐,使用时需注意。

猜你喜欢

转载自blog.csdn.net/qq_30035133/article/details/86367529