说明:如果多个DML语句是同一个连接对象操作的,他们的算作同一个事务,前提是关闭自动提交。
事务切面开启注解@EnableTransactionManagement,由注解上的@Import(TransactionManagementConfigurationSelector.class)
来到selectImports方法。PROXY代表JDK事务,引入了AutoProxyRegistrar和ProxyTransactionManagementConfiguration。
AutoProxyRegistrar的作用是注册aop的入口类InfrastructureAdvisorAutoProxyCreator,把属性设置到入口类中,最终会copy到proxyFactory中。Bean实例化最后一步判断是否有切面,如果有则生成代理。
ProxyTransactionManagementConfiguration中定义了事务切面BeanFactoryTransactionAttributeSourceAdvisor。
首先在事务切面里面设置处理事务属性对象TransactionAttributeSource,是由下面transactionAttributeSource方法创建的。这个是@Transactional注解的属性解析类,解析完注解后将属性封装成transactionAttribute对象。在transactionAttributeSource中可以看到它是AnnotationTransactionAttributeSource对象。
enableTx是@EnableTransactionManagement中的排序属性,是一个map,根据这个属性可以排序切面。
事务切面会设置advice,是TransactionInterceptor类型,由下面的transactionInterceptor定义,它继承了MethodInterceptor。
在transactionInterceptor中可以看到如果txManager不为空,advice就设置了事务管理器,它是在父类AbstractTransactionManagementConfiguration中定义的。由setConfigurers可以依赖注入TransactionManagementConfigurer,在annotationDrivenTransactionManager方法中返回事务管理器,这就要求定义一个类实现TransactionManagementConfigurer,如下图所示。启动过程中没有txManager无影响,一般也不用这种方式,常用的会在后面说。
每个切面都会有Pointcut和Advice。首先看事务切面的PointCut,它定义在BeanFactoryTransactionAttributeSourceAdvisor中,在代理生成之前会调用ClassFilter的matches和MethodMatcher的matches。
ClassFilter。TransactionAttributeSourcePointcut第一行就定义了ClassFilter为TransactionAttributeSourceClassFilter,但是这个
ClassFilter其实什么都没做。
看到它的matches方法,关键代码是tas.isCandidateClass(clazz)
。这里的TransactionAttributeSource为AnnotationTransactionAttributeSource,看到它的isCandidateClass。
isCandidateClass中循环调用了annotationParsers容器中的TransactionAnnotationParser.isCandidateClass。annotationParsers是在上面AnnotationTransactionAttributeSource中定义的,这里起作用的也就只有
SpringTransactionAnnotationParser。
在SpringTransactionAnnotationParser的isCandidateClass中可以看到,它是在找java.开头的注解和java.开头的类。@Transactional注解是Spring定义的,事务相关的类是自定义的,所以都不符合,返回true。于是ClassFilter的matches一直都是返回true。
MethodMatcher。TransactionAttributeSourcePointcut继承了StaticMethodMatcherPointcut,StaticMethodMatcherPointcut继承了StaticMethodMatcher,StaticMethodMatcher实现了MethodMatcher,所以TransactionAttributeSourcePointcut本身就是MethodMatcher,它里面定义了MethodMatcher的matches方法。
matches方法会调用2次, 第一次是找切面的过程,代理对象生成之前会调用一次,第二次是在走拦截器链的时候又会被调用。ClassFilter的matches是一直返回为true的,也就是说只要方法上面能拿到@Transactional注解就生成代理。这里是拿事务属性,拿事务属性就相当于有@Transactional注解。
看到AbstractFallbackTransactionAttributeSource的getTransactionAttribute,首先从缓存中取TransactionAttribute,如果不为空直接返回,如果为空,则需要在computeTransactionAttribute中得到TransactionAttribute。
computeTransactionAttribute中首先判断方法是否为public,如果是非public方法则返回null不会生成代理。获取原始方法,AopUtils.getMostSpecificMethod(method, targetClass);
很有用,它总能拿到原始方法(因为生成代理之后,一般拿到的都是方法的代理)。
获取方法上面@Transactional注解的属性,点进去后来到AnnotationTransactionAttributeSource的determineTransactionAttribute,方法中主要是通过SpringTransactionAnnotationParser的parseTransactionAnnotation,拿到@Transactional的属性并封装成TransactionAttribute对象。其中,attributes相当于map,存的都是@Transactional的属性,parseTransactionAnnotation中拿到属性封装成对象。
如果方法上没有注解,再通过findTransactionAttribute去类上找注解。点进去可以看到它也是来到AnnotationTransactionAttributeSource的determineTransactionAttribute,逻辑和上面从方法上找注解是一样的。
再回到AbstractFallbackTransactionAttributeSource的getTransactionAttribute,如果TransactionAttribute不为空,将它放入缓存,然后返回。拿到了事务属性返回true,生成代理。
Advice。事务切面的Advice即TransactionInterceptor,看到它的invoke方法,invocation::proceed
很明显就是一个链式调用。
进入invokeWithinTransaction。因为ProxyTransactionManagementConfiguration的transactionInterceptor中在依赖注入后将TransactionAttributeSource设置到advice中了,所以能通过getTransactionAttributeSource拿到事务属性类AnnotationTransactionAttributeSource,然后获取事务属性和事务管理器。
determineTransactionManager中可以看到如果事务管理器为空,从Spring容器中拿到TransactionManager类型的类作为事务管理器。也就是说,只要自定义一个TransactionManager,注册到Spring容器中,就能引入事务管理器。下面白图中的DataSourceTransactionManager就是TransactionManager,一般都是用这种方式引入事务管理器。
asPlatformTransactionManager是校验事务管理器,methodIdentification是获取方法名称。
还是在invokeWithinTransaction方法中。
进入createTransactionIfNecessary后,tm.getTransaction(txAttr);
主要是开启事务。
进入tm.getTransaction(txAttr);
再进入doGetTransaction();
DataSourceTransactionObject是事务对象,里面有个newConnectionHolder标识,标识是否是新连接。事务肯定是跟连接对象挂钩的,父类JdbcTransactionObjectSupport中定义了当前连接对象的包装类ConnectionHolder,可以理解为数据库连接对象。JdbcTransactionObjectSupport还定义了连接对象属性相关的信息,包括隔离级别、是否只读、是否允许回滚。所以DataSourceTransactionObject是跟连接对象有关的一些包装。
接着看doGetTransaction方法,txObject.setSavepointAllowed(isNestedTransactionAllowed());
含义为是否创建回滚点,isNestedTransactionAllowed()默认返回的是true。
obtainDataSource()是获取数据源对象,即上面例子中设置的数据源,TransactionSynchronizationManager.getResource是根据dataSource从ThreadLocal中获取到连接对象,第一次是没有值的,为null。ThreadLocal的结构是Map
。
拿到连接对象后放到事务对象中,设置newConnectionHolder为false。
isExistingTransaction(transaction)
是判断事务对象中的连接对象是否为空。第一次进来connectionHolder为空,所以if不会走,看到下面的代码。
第一次进来会首先判断事务的传播属性,事务的传播属性是用来控制事务流转的。传播属性为REQUIRED、REQUIRES_NEW、NESTED执行下面的代码。传播属性默认为REQUIRED。
首先将事务挂起,然后执行startTransaction。
startTransaction中下面的代码是创建一个新的事务状态。newTransaction为true,这是核心。DefaultTransactionStatus对象主要是标识事务的一些属性,是否是最新事务,是否是旧的事务,Spring可以通过它来控制事务的传播。
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
在newTransactionStatus方法中可以看到把事务对象、状态标识、是否挂起等属性都传到了DefaultTransactionStatus中返回。
doBegin(transaction, definition);
开启事务。首先判断事务对象中有没有连接对象,如果没有连接对象,从dataSource中拿到连接对象,包装成ConnectionHolder后设置到事务对象中,并把newConnectionHolder设置为true。
设置有事务标识,设置是否只读连接和事务隔离级别到连接对象中,设置该连接的上一个隔离级别。如果是自动提交,关闭自动提交。
把事务活跃状态设置为true,设置事务超时时间。首先判断newConnectionHolder是否为true,现在是为true的。TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
是绑定线程、dataSource、连接对象。在这里可以看到线程对应的是map,map中是数据源对应连接对象。
从doBegin出来后来到prepareSynchronization,这里是改变TransactionSynchronizationManager中的事务状态。DefaultTransactionStatus是Spring内部流转的事务状态,TransactionSynchronizationManager相当于开发人员使用的事务状态工具类,可以拿到事务的流转状态,是否已经提交、回滚。
从startTransaction出来后来到prepareTransactionStatus。每走到一步事务状态都是不一样的,事务状态是实时在变的,这里设置newTransaction为true,最后返回DefaultTransactionStatus。
最后从tm.getTransaction(txAttr);
跳出,来到return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
把事务管理器、事务属性、joinpoint和事务状态包装成TransactionInfo。
从TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
跳出,retVal = invocation.proceedWithInvocation();
调用被代理方法,进行链式调用。completeTransactionAfterThrowing(txInfo, ex);
做事务回滚,commitTransactionAfterReturning(txInfo);做事务提交。
进入commitTransactionAfterReturning(txInfo);
,进入txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
的commit,processCommit(defStatus);
执行事务提交。如果是required事务doBegin的时候isNewTransaction()为true。这时,提交事务。
Spring事务的传播属性包括REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED。这里只在源码层面介绍REQUIRED、REQUIRES_NEW和NESTED。
先看下面的代码,transaction上有@Transactional注解,方法中调用了areaService.addArea方法和goodsService.addGoods方法,这两个方法上面也都有@Transactional注解。
@Transactional
public void transaction(AreaInfo areaInfo, Good good) {
ConnectionHolder resource = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
System.out.println(resource.getConnection());
areaService.addArea(areaInfo);
goodsService.addGoods(good);
}
@Transactional
public int addArea(AreaInfo areaInfo) {
ConnectionHolder resource = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
System.out.println(resource.getConnection());
int i = commonMapper.addArea(areaInfo);
return i;
}
@Transactional
public int addGoods(Good good) {
ConnectionHolder resource = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
System.out.println(resource.getConnection());
int i = commonMapper.addGood(good);
return i;
}
根据上面的代码和Spring源码可以整理出下面的伪代码。
transaction方法执行开始
try{
// 开启事务
createTransactionIfNecessary
areaService.addArea(areaInfo);
addArea方法执行开始
try{
// 开启事务
createTransactionIfNecessary
int i = commonMapper.addArea(areaInfo);
return i;
} catch(Throwable ex) {
// 事务回滚
throw ex;
}
// 事务提交
commitTransactionAfterReturning(txInfo);
addArea方法执行结束
goodsService.addGoods(good);
addGoods方法执行开始
try{
// 开启事务
createTransactionIfNecessary
int i = commonMapper.addGood(areaInfo);
return i;
} catch(Throwable ex) {
// 事务回滚
throw ex;
}
// 事务提交
commitTransactionAfterReturning(txInfo);
addGoods方法执行结束
} catch(Throwable ex) {
// 事务回滚
throw ex;
}
// 事务提交
commitTransactionAfterReturning(txInfo);
transaction方法执行完成
上面的测试代码先执行transaction方法的createTransactionIfNecessary开启事务,在doBegin中创建连接对象。
接着执行addArea方法的createTransactionIfNecessary,来到AbstractPlatformTransactionManager的getTransaction方法。
当前是有连接对象的,所以能进到handleExistingTransaction。
REQUIRED传播属性不会走任何的if条件,到最后一行代码才返回。定义newTransaction为false,这是决定事务是否回滚、提交的关键。
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
在事务回滚completeTransactionAfterThrowing中可以下面的代码,当newTransaction为true才会回滚。
在事务提交中也可以看到newTransaction为true才会提交事务。
所以,当传播属性为REQUIRED,areaService.addArea方法和goodsService.addGoods方法出现异常不会回滚事务只会往上抛异常,正常执行也不会提交事务。只在执行transaction方法的completeTransactionAfterThrowing或commitTransactionAfterReturning时做事务的统一回滚或提交。
思考问题一:
在最外层用try…catch…捕获异常,只做打印的话,如果addArea或addGoods抛出异常还会事务回滚吗?
@Transactional
public void transaction(AreaInfo areaInfo, Good good) {
try {
ConnectionHolder resource = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
System.out.println(resource.getConnection());
areaService.addArea(areaInfo);
goodsService.addGoods(good);
} catch (Exception e){
e.printStackTrace();
}
}
来到事务回滚的代码中,当addArea或addGoods抛出异常,会设置事务对象中连接对象的rollbackOnly为true。
因为我们捕获了异常,所以会执行transaction方法的commitTransactionAfterReturning做事务提交。由下面的代码可以看到,当连接对象的rollbackOnly为true时,会做事务回滚。
在最外层捕获异常会做事务回滚。如果在areaService.addArea或goodsService.addGoods中捕获异常则不会做事务回滚。
给addArea方法的传播属性定为REQUIRES_NEW。
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int addArea(AreaInfo areaInfo) {
ConnectionHolder resource = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
System.out.println(resource.getConnection());
int i = commonMapper.addArea(areaInfo);
return i;
}
执行addArea方法的createTransactionIfNecessary,在handleExistingTransaction中先将前一个事务挂起。
在suspend的doSuspend中,把连接对象设置为null,将前一个事务的连接对象和当前线程解除绑定。
把包装了挂起连接对象的对象返回。
SuspendedResourcesHolder suspendedResources = suspend(transaction);执行完后在startTransaction中开启事务,在startTransaction中把suspendedResources封装在事务状态中。
addArea方法在processRollback中回滚事务或在processCommit中提交事务后,finally中处理挂起事务,恢复前面的连接。
doCleanupAfterCompletion是释放当前连接。也是将线程和当前连接对象解绑的操作,并且设置自动提交为true。方法中还执行了DataSourceUtils.releaseConnection,这个里面执行了con.close();
con一般为数据库连接池创建的代理对象,con.close();相当于将连接释放到连接池。
在resume方法里面恢复绑定关系,doResume中将当前线程重新和挂起的transaction方法的连接对象绑定。伪代码中,在goodsService.addGoods方法执行createTransactionIfNecessary开启事务时,doGetTransaction()可以拿到transaction方法的连接对象,接着执行下面的代码。
将addArea和addGoods的传播属性都定为NESTED,addGoods中抛出异常。
@Transactional(propagation = Propagation.NESTED)
public int addArea(AreaInfo areaInfo) {
ConnectionHolder resource = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
System.out.println(resource.getConnection());
int i = commonMapper.addArea(areaInfo);
return i;
}
@Transactional(propagation = Propagation.NESTED)
public int addGoods(Good good) {
ConnectionHolder resource = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
System.out.println(resource.getConnection());
int i = commonMapper.addGood(good);
throw new RuntimeException();
return i;
}
看上面的伪代码,先来到transaction方法事务切面的createTransactionIfNecessary开启事务。接着是addArea方法的createTransactionIfNecessary,可以拿到连接对象,于是进入handleExistingTransaction,来到NESTED的if语句。
useSavepointForNestedTransaction()默认返回true,可以嵌套事务。创建了事务状态,newTransaction为false。没有创建连接对象。
status.createAndHoldSavepoint();
是拿到事务对象中的对接对象,创建回滚点。看到最后的实现,是利用Connection.setSavepoint()创建回滚点SAVEPOINT_1、SAVEPOINT_2…最后把回滚点设置到事务状态中。
所以addArea方法或addGoods方法的createTransactionIfNecessary仅仅是在事务切面中创建了回滚点。
执行addArea的被代理方法,最后执行addArea的事务提交,来到下面的代码。
进入releaseHeldSavepoint,可以看到这里把addArea的回滚点抹掉了。连接对象执行了releaseSavepoint,事务状态的回滚点设为null。
接着执行addGoods的createTransactionIfNecessary,同样也是创建了回滚点,执行被代理方法。但是addGoods抛出了异常,所以会来到completeTransactionAfterThrowing,走到下面的代码。
进入status.rollbackToHeldSavepoint()的getSavepointManager().rollbackToSavepoint(savepoint)。
根据回滚点回滚了sql,conHolder.resetRollbackOnly()是把连接对象的rollbackOnly置为了false。
接着看伪代码,addGoods抛出异常后由transaction方法的事务切面捕获,来到下面的代码。
transaction方法是没有回滚点的,且newTransaction属性为true,所以会执行doRollback(status);
执行全部回滚,也就是说会回滚addArea方法的sql。
这里可以小小的总结一下。非第一次创建事务,并且传播属性是nested才有回滚点。如果是第一次创建事务,就算传播属性是nested也没有savepoint,因为没有连接对象。
思考问题二:
如何保证addGoods抛出异常时addArea正常提交?
我们看到,addGoods在做回滚的时候,会将连接对象的rollbackOnly设置为false。
看一下提交事务commitTransactionAfterReturning对于该字段是怎么处理的,也就是下面的处理逻辑,当连接对象的rollbackOnly为true时回滚事务。连接对象的rollbackOnly为false会执行事务提交。
这里的rollbackOnly属性为什么从事务对象中拿?因为涉及到多个事务切面传递参数,多个事务切面的事务对象不是同一个,但是连接对象绝对是同一个,可以看到最终是从连接对象中拿到rollbackOnly。
回到问题,接对象的rollbackOnly为false,执行事务提交可以正常提交,所以在transaction方法中加try…catch…即可。
@Transactional
public void transaction(AreaInfo areaInfo, Good good) {
try {
ConnectionHolder resource = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
System.out.println(resource.getConnection());
areaService.addArea(areaInfo);
goodsService.addGoods(good);
} catch (Exception e) {
e.printStackTrace();
}
}
在之前的源码中我们可以看到,Spring执行事务提交不一定会提交事务,那Spring执行事务回滚是否一定会回滚事务?
在addGoods的@Transactional注解里加上属性rollbackFor = TransactionTestException.class,TransactionTestException继承RuntimeException。
public class TransactionTestException extends RuntimeException {
}
@Transactional(propagation = Propagation.NESTED, rollbackFor = TransactionTestException.class)
public int addGoods(Good good) {
ConnectionHolder resource = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
System.out.println(resource.getConnection());
int i = commonMapper.addGood(good);
throw new RuntimeException();
return i;
}
进入事务回滚completeTransactionAfterThrowing的txInfo.transactionAttribute.rollbackOn(ex),最后来到RuleBasedTransactionAttribute.rollbackOn(Throwable ex)。
rollbackRules是List,里面存的是TransactionTestException,抛出的异常ex为RuntimeException。
进入判断方法getDepth。如果当前抛出的异常ex和rollbackFor定义的异常类名是一样的,返回depth,不然就一直找ex的父类去和rollbackFor定义的匹配,最后返回递归的深度depth。如果找到异常的最高级Throwable还不能匹配,就返回-1。
看到rollbackOn方法,depth >= 0时winner才有值,当depth 返回-1时winner为nul,会执行super.rollbackOn(ex);
来到DefaultTransactionAttribute的rollbackOn方法,判断ex是否为RuntimeException或Error。
这里的例子抛出的是RuntimeException,所以会执行事务回滚。如果抛出的异常在getDepth和DefaultTransactionAttribute的rollbackOn方法中都不匹配,则会提交事务。
注解@Transactional控制粒度太大,当有2个方法是不同的事务时,第2个方法执行时间过长,前面还有事务被挂起,这样连接对象就会浪费。可以用TransactionTemplate做编程式事务。
TransactionTemplate中定义的传播属性是REQUIRED,所以不用管传播属性,每次调用都是单独的事务。
创建TransactionTemplate,设置事务管理器。
@Bean
public PlatformTransactionManager annotationDrivenTransactionManager(DataSource dataSource) {
DataSourceTransactionManager dtm = new DataSourceTransactionManager();
dtm.setDataSource(dataSource);
return dtm;
}
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager platformTransactionManager) {
TransactionTemplate transactionTemplate = new TransactionTemplate();
transactionTemplate.setTransactionManager(platformTransactionManager);
return transactionTemplate;
}
依赖注入使用TransactionTemplate。
@Autowired
private TransactionTemplate transactionTemplate;
public int getTicketModeOne(Good good) {
return transactionTemplate.execute(status -> {
return commonMapper.addGood(good);
});
}
我们可以在事务提交、回滚时做一些自己的业务。
举个例子,事务提交后会执行下面的代码
最后是循环调用TransactionSynchronization。
TransactionSynchronization是一个接口,里面定义的方法可以在事务任何环节做相关业务操作。但不是任何方法都是我们要关注的,这个时候可以用适配器模式,定义抽象类实现接口,然后用一个类继承抽象类,实现关注的方法。
TransactionSynchronizationAdapter是Spring为我们定义的适配器。看下面的代码,我们可以用这个类在事务提交后做业务操作。
public class DoOnAfterCommit extends TransactionSynchronizationAdapter {
@Override
public void afterCommit() {
super.afterCommit();
System.out.println("=========事务提交后做事情==========");
}
}
@Transactional()
public List<ConsultConfigArea> queryAreaFromDB(Map param) {
// 注册触发器
TransactionSynchronizationManager.registerSynchronization(new DoOnAfterCommit());
logger.info("================从mysql里面查询数据 事务1========================");
List<ConsultConfigArea> areas = commonMapper.queryAreaByAreaCode(param);
return areas;
}