今天遇到了个Spring的@Transactional注解用法的问题(从此也可以看出自己数据库的薄弱,还是需要补这个基础).经过一个下午的源码阅读和debug终于解开了心中所惑,遂写下来记录一下.

目录

问题是什么?

首先讲下场景.我需要对几张表做修改操作,但是在修改之前我需要先查询这几张表做数据校验.然后修改这些数据.修改操作我希望是一个原子性操作,于是很自然的想到了Spring的@Transactional注解自动事务管理. 我把我的代码简化一下:

public class Controller{
	@Autowired
	private Service service;
    
    public void save(){
    	service.save();
    }
}
public class Service{
	public void save(){
    	//......省略查询表,校验数据步骤
        doSave();
    }
    @Transational
    public void doSave(){
    	//修改数据步骤省略
    }
}

一开始我觉得完美.然后开开心心的提交了代码.直到sonar反馈给我一个bug:

然后我就傻眼了…虽然我看不懂提示的内容(所以只截图了部分内容),但是我知道不对.那是为什么呢?

sonar的解释: When using Spring proxies, calling a method in the same class (e.g. this.aMethod()) with an incompatible @Transactional requirement will result in runtime exceptions because Spring only “sees” the caller and makes no provisions for properly invoking the callee.

Therefore, certain calls should never be made within the same class:

找资料

由于我很多年没怎么用数据库,所以很多东西往得差不多了,所以需要先补补理论基础.于是我看了这个.初步了解了spring事务的原理是使用了aop.那么是不是每个方法都会用aop拦截,然后在方法前创建事务,方法后关闭事务?那如果是这样的话save()并没有使用事务,也不会影响到doSave()的事务啊。带着疑问我继续找资料.看了这个源码分析

如果你跟我一样基础不怎么好,一定要看完这两篇文章之后再往下看

debug

看完上面两篇文章之后,我开始了debug之路.在TransactionInterceptor的invoke方法做了断点.发现压根就没进去过.但是我把代码改成这样:

public class Controller{
	@Autowired
	private Service service;

    public void save(){
    	service.save();
        service.doSave();
    }
}
public class Service{
	public void save(){
    	//......省略查询表,校验数据步骤
    }
    @Transational
    public void doSave(){
    	//修改数据步骤省略
    }
}

之后当执行到service.doSave()代码之后,就会进入到断点位置(TransactionInterceptor的invoke方法).invoke里面的内容就是开启事务,commit,rollback等事务的逻辑(即@Transational生效了,开启了事务). 但是这又是为什么? 为何save()直接调用doSave时无法开启事务呢?于是又找到了一篇文章. 文章最后的结论是: 动态代理最终都是要调用原始对象的,而原始对象再去调用方法时,是不会再触发代理了。

结合它的结论,再根据代码分析,最后我的结论是: 由于spring的事务管理是使用动态代理来实现的.而动态代理在调用是在调用其他类时生效(就是controller调用service的方法时才生效)。从最初的代码可以看到controller在调用service.save()时是能进入invoke方法的。但是由于save()方法没有使用@Transational,所以并没有进入invoke。然后saove()调用doSave()时动态代理没有生效(同一个类的内部方法调用不满足动态代理生效的机制).

最后结论

如果要使@Transational生效,需要从外部类(controller或者其他类)调用.这样可以似的动态代理生效,从而进入TransactionInterceptor的invoke方法最终开启事务。