阅读源码-@Transaction

@Transaction事务注解中rollBackFor和noRollbackFor底层执行逻辑

方法论

方法论-关注调用栈

栈底调用栈顶

image-20230510144221088

通过调用栈找到了事务相关的方法

org.springframework.transaction.interceptor.TransactionInterceptor#invoke(MethodInvocation)

image-20230510145927339

重新打上断点,恢复程序后,重新发送请求。从这里开始正向调试,从框架代码一步一步往业务代码执行。

步入方法,继续调试

org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

image-20230510150428427

方法论-开启Debug日志

通过观察日志的输出,寻找阅读源码突破口

1
2
3
logging:
level:
root: trace

image-20230510155500459

寻找日志输出的地方,重新打上断点后,重新调试程序。

方法论-查看被调用的地方

直接查看注解在框架哪些地方被调用

image-20230510152743919

当调用的地方比较少时,直接在调用的地方打上断点就可以

排除项目类、测试类、注释等后,继续找,找到SpringTransactionAnnotationParser类,但是该类仅仅是项目启动时解析Spring事务注解而已。

image-20230510152856977

image-20230510152948857

当以上几个地方打上断点时,仅仅在项目启动过程中断点起了作用,发起调用的时候并没有在断点处停留,说明发起调用时并不会触发这部分逻辑。

换个思路,@Transaction注解在项目启动时加载,但是注解中的值rollbackFor和noRollbackfor应该在程序调用的时被调用。

image-20230510154125286

发现这些值只有在业务代码和注释上出现,但是我们发现注释上提到

org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn

image-20230510154333706

image-20230510154432569

遇到一个接口好多实现类的情况下,可以直接在接口上打上断点,重新调试后,断点会自动停留在具体执行的实现类上

image-20230510155753565

在接口上打上方法断点,会走到这个实现类上中

org.springframework.transaction.interceptor.DelegatingTransactionAttribute#rollbackOn

image-20230510155638744

观察此时的程序调用栈,寻找到了突破口

image-20230510160546088

一些知名框架的JavaDoc注释中,有很多关键的@see和@link信息,并且是最权威的正确信息,比官方文档或者技术博客清晰稳妥。

搜索答案

寻找到了突破口即最开始的地方,接下来就是反复的调试,一步一步的往下调试。

从程序调用栈起点开始

org.springframework.transaction.interceptor.TransactionInterceptor#invoke

image-20230510162607981

选择步入方法

org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

image-20230510162541553

重点逻辑如下:执行业务逻辑代码,然后基于业务逻辑处理结果,然后去走不同的逻辑。

抛出异常,执行方法:completeTransactionAfterThrowing()

正常执行完毕,执行方法:commitTransactionAfterReturning()

image-20230510162858976

步入completeTransactionAfterThrowing后,继续调试,可以看到当前事务和当前异常的相关的信息

image-20230510163501679

在方法里,大体逻辑如下

image-20230510164236120

其中判断当前抛出异常是否要回滚,就是这里的重中之重。

调试过程中,遇到if-else语句要注意去构建不同的案例,以覆盖尽量多的代码逻辑。

步入判断逻辑方法中

org.springframework.transaction.interceptor.DelegatingTransactionAttribute#targetAttribute

image-20230510164805752

继续步入方法

org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn

image-20230510165650941

这里就是就是判断该异常是否回滚的具体逻辑:

核心逻辑:rollbackRules中配置了注解中的回滚规则,通过循环,拿到我们程序中抛出的异常ex,去匹配规则,最好选择一个winner

image-20230510165908804

如果winner为空,则走默认逻辑,如果是RuntimeException 或者是 Error 的子类,就要进行回滚。即当没有配置rollbackFor与noRollbackFor的情况下,如果抛出的异常属于RuntimeException 或者 Error 的子类,则进行回滚操作

image-20230510170720869

如果winner不为空,则查看winner是否属于NoRollbackRuleAttribute即不用回滚的配置,如果是则返回false,表示不用执行回滚操作

image-20230510171624911

如今的核心的问题是winner怎么来的,答案就就在如下递归调用中

image-20230510172110046

核心理论就是:如果配置的规则不为空,则判断当前抛出的异常和配置的规则中的 rollbackFor 和 noRollbackFor 谁距离更近。这里的距离是指父类与子类之间的关系。将更近的作为winner放到return !(winner instanceof NoRollbackRuleAttribute);中进行判断

抛出的是 RuntimeException,它距离 noRollbackFor=RuntimeException.class 为 0。RuntimeException 是 Exception 的子类,所以距离 rollbackFor = Exception.class 为 1。winner