前言
本文主要记录笔者学习Transaction Management的学习路径,读者可以对比自己的学习路径,一起讨论出探讨出更优学习路径。
基本路径
找到官方文档。
找到Overview页面,了解项目提供的能力、能实现的效果、设计理念、历史、最低要求等等。
找到Quick Start,快速把它的环境搭建起来,对项目有个感性的认知。
找到想了解的模块
找到模块的Introduction,一般这里有项目的基本介绍,你需要找到这个模块用到的基本概念或者名词介绍文档。
使用场景、功能特性以及相关生态系统介绍
了解核心/共用/复用模块
确定学习目标(遇到的问题、心存疑问/好奇),以点带面的阅读方式来阅读源码
提炼升华
结合实际开发/项目,可以解决什么问题、原来处理有什么不足/缺陷
日常思考、联想、完善
接下来将根据基本路径展开,源码时序图只画出核心点,具体还需读者自行调试。
Spring Framework Overview
参考
Quick Start
传送门
想要了解的模块
Transaction Management
使用场景和功能特性
Spring Framework全面支持事务,为事务管理提供了一致性的抽象。跨不同事务API的一致性编程模型。支持声明式事务管理。与Spring的数据库访问抽象的完美集成。
核心
通过阅读前面文章,使用声明式事务的话,可以大概知道Spring Framework的核心有事务策略的抽象-PlatformTransactionManager、管理每个线程的资源和事务同步的中央委托-TransactionSychrnoizationManager、使用AOP代理包装合适的bean的一种BeanPostProcessor-AbstractAutoProxyCreator、配合PlatformTransactionManager实现驱动事务around method的TransactionInterceptor。
下面将通过一个简单的事务示例(使用mysql、mybatis-plus、spring-boot),画一个时序图来看看核心类起到的作用。
示例
importlombok.SneakyThrows;importorg.mybatis.spring.annotation.MapperScan;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.annotation.AdviceMode;importorg.springframework.transaction.annotation.EnableTransactionManagement;@SpringBootApplication@EnableTransactionManagement@MapperScan(basePackages={"com.whf.spring.dao"})publicclassApplication{@SneakyThrowspublicstaticvoidmain(String[]args){SpringApplication.run(Application.class,args);}}importcom.whf.spring.service.business.TryService;importorg.junit.jupiter.api.Test;importorg.junit.runner.RunWith;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.test.context.junit4.SpringRunner;importorg.springframework.test.context.web.WebAppConfiguration;@RunWith(SpringRunner.class)@SpringBootTest(classes=Application.class)@WebAppConfigurationclassApplicationTests{@AutowiredprivateTryServicetryService;@TestvoidtestTransaction(){tryService.test();}}
importcom.whf.spring.annotation.SimpleAnnotation;importcom.whf.spring.service.UserService;importcom.whf.spring.service.business.TryService;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.ApplicationEventPublisher;importorg.springframework.context.annotation.Scope;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;@Service@Scope@Slf4jpublicclassTryServiceImplimplementsTryService{@AutowiredprivateUserServiceuserService;@Override@SimpleAnnotation@Transactional(rollbackFor=Exception.class)publicvoidtest(){log.info("执行业务");userService.test();}}
importcom.whf.spring.dao.UserDao;importcom.whf.spring.entity.UserDo;importcom.whf.spring.service.UserService;importorg.springframework.stereotype.Service;importcom.baomidou.mybatisplus.service.impl.ServiceImpl;@Service("userService")publicclassUserServiceImplextendsServiceImpl<UserDao,UserDo>implementsUserService{@Overridepublicvoidtest(){this.selectById(1);thrownewRuntimeException();}}
大致调用如下:
源码时序图
核心的顺序应该是:
AbstractAutoProxyCreator 创建代理
调用事务必经过TransactionInterceptor
TransactionInterceptor会调用PlatformTransactionManger进行事务管理
PlatformTransactionManger会调用TransactionSychrnoizationManager进行事务资源同步
核心类的源码如下,围绕核心的一些其他源码时序图在其他章节会涉及。
创建代理时序图
可以看到,代理的生成发生在初始化后期,@Transactional是可以放在接口上的,@Transactional只有放在public方法上才生效,最终使用的是cglib代理而不是jdk动态代理。
调用时序图
调用主要围绕TransactionInterceptor展开,around事务业务方法进行一些处理,例如before method时获得TransactionAttributeSource、确定具体的TransactionManager、根据事务上下文和传播行为决定是否创建新的事务等、绑定资源、解绑资源。
确定学习目标
遇到问题
EnableTransaction使用默认的PROXY基于接口的代理,为啥不实现接口@Transactonal也可以用 详见下面@EnableTransactionManagement源码章节
每次都要学rollbackFor,怎么全局写 可以自定义事务注解
@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Transactional(rollbackFor=Exception.class)public@interfaceDefaultTransactional{}
心存疑问/好奇
@EnableTransactionManagement属性的影响? 详见下面“@EnableTransactionManagement”源码章节
如何注入被事务代理的bean? 详见下面“事务代理bean的注入”源码章节
Spring声明式事务是如何实现的? 详见下面“Spring声明式事务的具体实现”章节
Spring如何实现对资源的管理(创建、重用和清理) 详见下面“Spring如何实现对资源的管理”源码章节
Spring如何处理物理事务和逻辑事务,不同的propagation是怎么处理的 详见下面“Spring如何处理物理事务和逻辑事务,不同的propagation是怎么处理的”源码章节
@TransactionalEventListeners是怎么实现的 详见下面“@TransactionalEventListeners是怎么实现的”源码章节
事务切面和普通定义的@Aspect切面的先后顺序 详见下面“事务切面和普通定义的@Aspect切面的先后顺序”源码章节
学习目标涉及的源码
@EnableTransactionManagement
使用的还是核心中的代码示例
属性影响
整体可以看出@EnableTransactionManagement会影响全局的APC选择,还会影响 transactionAdvisor的顺序,方便处理存在多个advisor。
为啥可以不实现接口@Transactonal也可以用
至于为啥@EnableTransactionManagement默认AdviceMode.PROXY、proxyTargetClass=false不实现接口还能使用@Transactional是因为最终选择的APC为AnnotationAwareAspectJAutoProxyCreator。
为啥不在Application上注解@EnableTransactionManagement也会使用上事务
按照上面的时序图调试就可以发现,AdviceModeImportSelector#selectImports的入参不一样,加的话如上面的代理示例@Configuration就是我们自己写的Application,不加的话@Configuration就是TransactionAutoConfiguration springboot自动装配的。
事务代理bean的注入
发现和普通的注入类似。注入的tryService是提前被代理好的bean。
Spring声明式事务的具体实现
结合上面的创建代理时序图、调用时序图、@EnableTransactionManagement时序图,我们来大体梳理下。
首先需要在@Configuration上使用@EnableTransactionManagement注解,不管是在自动装配还是在你自己的@Configuration。这样相当于xml的<tx:annotation-driven/>
配置,表示启用基于事务注解的事务行为。同时确定了APC为AnnotationAwareAspectJAutoCreator、事务管理配置为ProxyTransactionManagementConfiguration。
ProxyTransactionManagementConfiguration确定了advisor为BeanFactoryTransactionAttributeSourceAdvisor、advice为TransactionInterceptor、事务属性资源AnnotationTransactionAttributeSource。这样就能使用BeanFactoryTransactionAttributeSourceAdvisor生成事务代理。
TransactionInterceptor设置了TransactionManager对事务进行管理,TransactionManager会使用到TransactionSynchronizationManager对资源进行同步。
Spring如何实现对资源的管理
首先要明白管理的对象是谁,下面用JDK原生写一个执行sql
Class.forName("com.mysql.jdbc.Driver");//加载驱动conn=DriverManager.getConnection(url);//获取数据库连接stmt=conn.createStatement();//创建执行环境stmt.execute(sql);//执行SQL语
主要就是对Connection的管理,创建连接、重用连接、清理连接。具体就是TransactionManager和TransactionSynchronizationManager的配合,具体可以根据上面的调用时序图进行了解。
Spring如何处理物理事务和逻辑事务,不同的propagation是怎么处理的
Spring事务底层是依赖于例如mysql之类的物理事务,一个Connection开启一个物理事务,逻辑事务是Spring根据Propagation来决定当前事务的行为(比如要挂起当前事务开启新的事务,还是加入当前事务),根据业务逻辑控制物理事务。一个物理事务对应一个Connection,一个Connection可能存在多个逻辑事务。
默认的Propagation.REQUIRED处理在上面的“调用时序图”可以了解到,下面将对常用的Propagation.PROPAGATION_NESTED传播行为进行讲解,通过对比默认的传播行为来更加清楚Spring对逻辑事务的处理,同时也能对事务回滚了解一二。具体如下图:
可以看到PROPAGATION_NESTED不会创建一个新的事务会对现有事务进行处理,更改TransactionStatus属性特别是savepoint,最后执行底层数据的“ROLLBACK TO SAVEPOINT”语句进行局部回滚(说明此种传播行为需要底层数据的支持),然后回到外层invokeWithTransaction,如果外层事务不想回滚直接catch住就可以了。
@TransactionalEventListeners是怎么实现的
TransactionalEventListener是一种ApplicationEventMulticaster会注册到TransactionSynchronizationManager中,TransactionSynchronizationManager提供一些事务钩子便可以根据事务不同阶段执行你发布的TransactionalEvent。
事务切面和普通定义的@Aspect切面的先后顺序
原始bean初始化之后会调用BeanFactory的applyBeanPostProcessorsAfterInitialization看是否有合适被代理,通过AnnotationAwareAspectJAutoProxyCreator找到所有拦截器(BeanFactoryTransactionAttributeSourceAdvisor 、InstantiationModelAwarePointcutAdvisor、ExposeInvacationInterceptor)并根据advisor的order进行排序生成一个具有多个advisor的代理。