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

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

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

@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;
    }
}

访问后执行结果以下:app

然而事务并无起做用~接着进行测试ide

示例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;
    }
}

访问后执行结果以下:this

事务起做用了,都没有修改为功!接下来咱们来个加强版,加上try后看下会有怎样的效果spa

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

@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后呢,会怎样呢?3d

示例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种传播行为:

一、PROPAGATION_REQUIRED:支持当前事务,若是当前不存在事务则新建一个。

二、PROPAGATION_SUPPORTS:支持当前事务,若是不存在,就不使用事务。

三、PROPAGATION_MANDATORY:支持当前事务,若是不存在,则抛出异常。

四、PROPAGATION_REQUIRES_NEW:若是当前有事务存在,挂起当前事务,建立一个新的事务。

五、PROPAGATION_NOT_SUPPORTED:以非事务方式运行,若是当前有事务存在,挂起当前事务。

六、PROPAGATION_NEVER:以非事务方式运行,若是当前有事务存在,抛出异常。

七、PROPAGATION_NESTED:若是当前存在一个事务,则该方法运行在一个嵌套的事务中。被嵌套的事务能够从当前事务中单独的提交和回滚。若是当前不存在事务,则开始一个新的事务。各厂商对这种传播行为的支持良莠不齐,使用时需注意。 

相关文章
相关标签/搜索