本文基于:Spring Boot 2.7.0
- 本文介绍了在Spring Boot框架下,我们通常会在Service类中的某个方法使用@Transactional注解来开启事务,Spring Boot在启动的时候是如何set up事务相关的配置。
- 以及在Spring Data JPA中的默认事务配置。
1. TransactionInterceptor
为什么重要?
在【每天学点Spring】Spring AOP APIs学习,以及在TransactionInterceptor中的应用中介绍了TransactionInterceptor
的重要方法,即invoke(invocation)
。
![](https://img.haomeiwen.com/i15522532/90cee5d2dc51fd4b.png)
下图可知TransactionInterceptor
继承了抽象类:TransactionAspectSupport
:
![](https://img.haomeiwen.com/i15522532/80207549fa630abf.png)
每个被标注@Transactional
注解的方法,都会经过TransactionInterceptor
的invoke
方法,在该方法中,发现调用了位于上层抽象类TransactionAspectSupport
的invokeWithinTransaction
:
![](https://img.haomeiwen.com/i15522532/d8597932ad382605.png)
在抽象类TransactionAspectSupport
中的invokeWithinTransaction
中,可以看到:
- 【Step-1】拿到当前的TransactionManager:
final TransactionManager tm = determineTransactionManager(txAttr);
- 【Step-2】是否需要重新创建Transaction:
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
- 【Step-3】目标方法的执行:retVal = invocation.proceedWithInvocation();
- 【Step-4】如果遇到错误,是否需要回滚:
completeTransactionAfterThrowing(txInfo, ex);
- 【Step-5】执行完目标方法后,clean transaction相关的信息:
cleanupTransactionInfo(txInfo);
- 【Step-6】返回前提交Transaction:
commitTransactionAfterReturning(txInfo);
下图是invoke调用链:
最右的...
一般是transactionManager,因为本文主要讨论TransactionInterceptor,所以略。
![](https://img.haomeiwen.com/i15522532/b6d50f3faed9d2c5.png)
回答本章节的问题:
TransactionInterceptor
为何重要?原因是它实现了MethodInterceptor
接口,本质上它的invoke方法即切面中的@Around
方法,通过切面实现了Transaction的创建(if needed),回滚(如果报错),提交(如果没有报错)。
它本身虽然不做Transaction的逻辑,但它是@Transactional
的入口。
2. TransactionInterceptor
类如何在Spring Boot的项目中被创建的
因为我们都知道Spring的项目只要标记注解@SpringBootApplication
,引入相应的Transaction
的包,就能直接使用@Transactional
的声明式注解了,那么是如何被创建的呢?
2.1 给TransactionInterceptor
构造器打断点
在TransactionInterceptor
构造器中打个断点,启动程序后,进入了断点,在调用链上看到上述的TransactionInterceptor
是通过类ProxyTransactionManagementConfiguration
创建的:
![](https://img.haomeiwen.com/i15522532/f1abb856f15cb59c.png)
2.2 找到ProxyTransactionManagementConfiguration
在ProxyTransactionManagementConfiguration
的类的注释上看到TransactionManagementConfigurationSelector
:
![](https://img.haomeiwen.com/i15522532/9424b0e3f8650ae4.png)
2.3 通过TransactionManagementConfigurationSelector
的selectImports创建了上述#2.2的类
在TransactionManagementConfigurationSelector
类的方法selectImports(adviceMode)
中可以看到返回了ProxyTransactionManagementConfiguration.class.getName()。
这个类是ImportSelector的实现类,所以方法selectImports返回的类名,最终会被实例化成Spring Bean。
![](https://img.haomeiwen.com/i15522532/060a7e2752f866e3.png)
2.4 通过给selectImports
方法打断点找到TransactionAutoConfiguration
查看调用链,可以看到最终是TransactionAutoConfiguration
的CglibAutoProxyConfiguration
类调用的上述的import类。
TransactionAutoConfiguration
位于spring-boot-autoconfigure-2.7.0.jar
包中:
![](https://img.haomeiwen.com/i15522532/88e6d2b570082cc0.png)
在文章【Spring Boot相关】@SpringBootApplication注解中有介绍到,一般的AutoConfiguration,可能会在spring-boot-autoconfigure-{version}.jar
中的META-INF/spring-autoconfigure-metadata.properties
,如下:
![](https://img.haomeiwen.com/i15522532/c01785ddb1c95372.png)
2.5 那么TransactionAutoConfiguration
中的CglibAutoProxyConfiguration
是怎么引用到#2.3的TransactionManagementConfigurationSelector
?
在CglibAutoProxyConfiguration
的截图中,可以看到类上的注解@EnableTransactionManagement
:
![](https://img.haomeiwen.com/i15522532/82e83dc06e14d8ad.png)
在@EnableTransactionManagement
的定义中可以看到通过Import注解引用了#2.3的TransactionManagementConfigurationSelector
:
![](https://img.haomeiwen.com/i15522532/71eeb9d8a1ea0c02.png)
【小结下】
Spring Boot项目通过AutoConfiguration的方式,引入了【TransactionAutoConfiguration
】,再通过cglib的代理类上的注解@EnableTransactionManagement
,最终创建了本文的类:【TransactionInterceptor
】。
@EnableTransactionManagement
位于spring-tx-5.3.20.jar包中。
它的官方在线文档:https://docs.spring.io/spring-framework/docs/5.3.20/javadoc-api/org/springframework/transaction/annotation/EnableTransactionManagement.html
@EnableTransactionManagement
激活了Spring的声明式事务的能力。类似于传统的xml中的<tx:annotation-driven/>
。
3. TransactionInterceptor
的PointCut设定
通过#2的EnableTransactionManagement
,只是创建了TransactionInterceptor
类,还需要一个Pointcut来对这个切面方法进行绑定(即需要告知切面一个入口)。
关于更多的Pointcut绑定,参考之前的文章:【每天学点Spring】Spring AOP APIs学习,以及在TransactionInterceptor中的应用 第#1.3的示例(如同AOP中的光定义@Around本身不够,还需要定义@Pointcut)。
Spring Boot是如何在启动的时候完成TransactionInterceptor与Pointcut绑定的呢?
思路:我们可以从@Transactional
注解出发,看看Spring是怎么解析这个注解的,那么也就知道了如何对TransactionInterceptor
进行绑定的了,在文件框索框中scope选择All Places:
![](https://img.haomeiwen.com/i15522532/07c9f0c178650b1d.png)
在SpringTransactionAnnotationParser
类的parseTransactionAnnotation
打断点,可以看到:
在Bean的initialize阶段,通过AnnotationAwareAspectJAutoProxyCreator
的postProcessAfterInitialization
方法中,就有判断,是否需要wrap(抽象类AbstractAutoProxyCreator
的wrapIfNecessary
方法):
![](https://img.haomeiwen.com/i15522532/1e647fea77ed06c8.png)
在AbstractAutoProxyCreator
中:如果需要wrap,则放入一个Map中,值为True,如果不需要wrap,则值为False:
![](https://img.haomeiwen.com/i15522532/b7c9ab274a81768c.png)
AbstractAutoProxyCreator
位于:spring-aop-5.3.20.jar中。
那么如果判断需不需要wrap?
这个则是在spring-tx-5.3.20.jar中判断的,在类TransactionAttributeSourcePointcut
中有个matches
方法:
![](https://img.haomeiwen.com/i15522532/e9de5598ceedec3f.png)
最终的最终,就会调用到我们全局搜索@Transactional注解的类SpringTransactionAnnotationParser
中,如果parse发现有该注解,则返回一个TransactionAttribute
,然后经过一系列的逻辑,判定需要warp,最终交于上述的spring-aop包进行wrap(即增强类):
![](https://img.haomeiwen.com/i15522532/de4ee6f950b6e479.png)
测试:
我们有两个service bean:
- courseService,方法上有@Transactional
- studentService,方法上没有@Transactional
在AbstractAutoProxyCreator
的wrapIfNecessary
方法中可以看到,标注了@Transactional的类是需要wrap的:
![](https://img.haomeiwen.com/i15522532/5e8509e13ae24c2c.png)
![](https://img.haomeiwen.com/i15522532/8f66661346ba8e13.png)
![](https://img.haomeiwen.com/i15522532/0a94276dfd730318.png)
![](https://img.haomeiwen.com/i15522532/fb3d00d775e40b93.png)
【总结】
- 分析了Spring Boot在启动的时候会读取定义的autoconfig类:
TransactionAutoConfiguration
,通过这个类的EnableTransactionManagement
实现TransactionInterceptor
的创建。 - 并在Bean create的时候,进行拦截,判断是否有
@Transactional
注解,如果有,则生成动态代理类。 - 这样,在运行的时候,动态代理类就会进入
TransactionInterceptor
的invoke方法,实现transaction的操作(如提交,回滚等)。
4. Spring Data JPA相关的事务
如果在上述的CourseServiceImpl中引入Spring Jpa的CourseRepository:
@Service("courseService")
public class CourseServiceImpl implements CourseService {
@Autowired
private CourseRepository courseRepository;
@Transactional
public List<Course> list() {
return courseRepository.findAll();
}
}
为了更好的观察,我们可以开启transaction的debug log,在application.yaml中配置:
logging:
level:
org.springframework.transaction.interceptor: trace
日志打印,我们可以看到,进入了两次TransactionInterceptor:
2022-10-01 18:41:34.683 TRACE 1288 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor : Getting transaction for [com.service.impl.CourseServiceImpl.list]
2022-10-01 18:41:34.695 TRACE 1288 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
2022-10-01 18:41:34.856 TRACE 1288 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
2022-10-01 18:41:34.857 TRACE 1288 --- [nio-8080-exec-1] o.s.t.i.TransactionInterceptor : Completing transaction for [com.service.impl.CourseServiceImpl.list]
可以看到除了CourseServiceImpl外,JpaRepository也会有transaction。
我们可以打个断点在commitTransactionAfterReturning
方法上,可以看到先是JpaRepository的transaction先commit,然后再是调用它的CourseServiceImpl,TransactionInterceptor
不是同一个Instance,但它里面重要的成员:transactionManager以及它的DataSource都是同一个。
![](https://img.haomeiwen.com/i15522532/18cb5fc130dd2fc9.png)
![](https://img.haomeiwen.com/i15522532/bda009309ce3c8d5.png)
另外,可以看到findAll()方法的Transactional是readOnly的。
即:默认情况下,repository 接口中的CRUD方法都是被@Transactional注解修饰了的。读的操作方法,@Transactional注解的readOnly属性是被设置为true的,就是只读状态;CRUD中的其他方法被@Transactional修饰,就是非只读。
关于Spring Data JPA set up,另写一篇文章。
Spring Data JPA文档:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/
网友评论