1. 前言
使用MySQL数据库时,使用事务与不使用事务相比,出现问题时排查更复杂。
不使用事务时,客户端只需要请求MySQL服务一次(只考虑显式执行的SQL语句);使用事务时,客户端至少需要请求MySQL服务四次(开启事务、执行SQL语句、提交/回滚事务、恢复自动提交)。
在Java中存在一些用法会导致事务失效,有的问题比较明显可以较快定位,有的问题隐藏较深可能需要较长时间排查。
因此需要对MySQL的事务执行原理进行分析,并整理用于排查事务相关问题的快速有效的方法。
在Java应用中访问MySQL服务时,涉及Java应用、网络传输、MySQL服务这三层,在每一层都可以对执行的SQL语句与事务操作进行监控与观测,涉及的内容如下图所示:
以下对Java应用使用Spring、MyBatis、Druid、MySQL Connector,使用事务执行SQL语句的情况进行分析。
可参考以下相关内容:
内容 | 链接 |
---|---|
MySQL SQL语句与事务执行及日志分析 | https://blog.csdn.net/a82514921/article/details/126563449 |
tcpdump、Wireshark抓包分析MySQL SQL语句与事务执行 | https://blog.csdn.net/a82514921/article/details/126563471 |
Spring、MyBatis、Druid、MySQL不使用事务执行SQL语句分析 | https://blog.csdn.net/a82514921/article/details/126563515 |
Spring、MyBatis、Druid、MySQL使用事务执行SQL语句分析 | https://blog.csdn.net/a82514921/article/details/126563542 |
Spring、MyBatis、Druid、MySQL执行SQL语句与事务监控 | https://blog.csdn.net/a82514921/article/details/126563558 |
数据源使用错误导致MySQL事务失效分析 | https://blog.csdn.net/a82514921/article/details/126563573 |
TiDB乐观事务、悲观事务模型验证 | https://blog.csdn.net/a82514921/article/details/126563502 |
2. 使用事务执行SQL语句大致阶段
在使用事务执行SQL语句时,每次执行SQL语句时的大致阶段如下:
从连接池借出连接(可能需要创建新连接)
关闭自动提交
执行SQL语句1
执行SQL语句n
提交/回滚事务
开启自动提交
归还连接至连接池
如下图所示:
使用事务执行SQL语句时,事务管理主要通过Spring完成,SQL语句执行主要通过MyBatis完成
3. 不使用与使用事务执行SQL语句的区别
3.1. SQL语句执行阶段的区别
- 不使用事务执行SQL语句
当不使用事务执行SQL语句时,每次执行SQL语句时,都需要先从连接池借出连接,再执行SQL语句,最后归还连接至连接池;
SQL语句会自动提交,不需要执行事务相关的阶段。
- 使用事务执行SQL语句
当使用事务执行SQL语句时,除了以上阶段外,还需要执行事务相关的阶段,包括:关闭自动提交、提交/回滚事务、开启自动提交等。
3.2. 对系统变量autocommit使用的区别
- 不使用事务执行SQL语句
当不使用事务执行SQL语句时,可一直使用MySQL系统变量autocommit的默认值(开启),不需要对系统变量autocommit进行修改与恢复;
- 使用事务执行SQL语句
当使用事务执行SQL语句时,需要先关闭当前连接会话的autocommit,以开启事务,再执行SQL语句
;
在归还连接至连接池之前,需要开启当前连接会话的autocommit,使该系统变量恢复为默认值开启(保证当前会话能够继续正常使用)
。
3.3. 对数据库连接使用的区别
在执行SQL语句时,无论是否使用事务,在从连接池借出连接,到归还连接至连接池这两个阶段之间的时间段,对应的连接是被当前线程独占的。
- 不使用事务执行SQL语句
当不使用事务执行SQL语句时,对于所使用的数据库连接没有特殊要求,每次执行SQL语句时从连接池获取一个可用的连接即可;
每条SQL语句执行完毕后,即可归还连接至连接池;
从连接池借出连接、归还连接至连接池的操作,通过MyBatis完成。
- 使用事务执行SQL语句
当使用事务执行SQL语句时,需要使用同一个连接执行当前事务的所有SQL语句(一条或多条)
;
执行完所有的SQL语句(以及提交/回滚事务、开启自动提交)之后,才能归还连接至连接池
。
从连接池借出连接、归还连接至连接池的操作,通过Spring完成。
4. 示例项目
以下使用的示例项目下载地址为:https://github.com/Adrninistrator/DB-Transaction-test,使用说明可参考“README.md”,
相关的执行日志保存在DB-Transaction-test-log目录中,log-print_stack_trace_off目录中是未打印调用堆栈的日志,log-print_stack_trace_on目录中是打印调用堆栈的日志。
5. 验证环境
- JDK版本
1.8
- Spring版本
5.3.20
- Mybatis版本
org.mybatis:mybatis:3.2.8
org.mybatis:mybatis-spring:1.2.2
- Druid版本
1.2.10
- MySQL Connector版本
8.0.29
- MySQL版本
MariaDB 10.0.36
- Spring事务传播机制
默认值REQUIRED
6. 通过@Transactional注解使用事务的处理步骤
通过Spring的@Transactional注解使用事务时,与使用事务模板TransactionTemplate存在一些区别。使用@Transactional注解时,会先经过Spring AOP的相关处理,在org.springframework.transaction.interceptor.TransactionAspectSupport类的invokeWithinTransaction()方法中,会进行事务相关的以下处理:
调用TransactionAttributeSource tas的getTransactionAttribute()方法,在该方法中获取@Transactional注解所在方法上的事务属性TransactionAttribute txAttr;
调用determineTransactionManager()方法,在该方法中根据事务属性txAttr获取事务管理器TransactionManager tm。在determineTransactionManager()方法中,优先使用@Transactional注解的value或transactionManager属性指定的事务管理器的名称;若未指定时则使用默认的事务管理器,默认的事务管理器名称可通过Spring XML中“tx:annotation-driven”元素的“transaction-manager”属性指定;
在示例项目中指定的事务管理器类型为org.springframework.jdbc.datasource.DataSourceTransactionManager,未实现ReactiveTransactionManager接口,因此不会执行满足“this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager”判断条件时才会执行的代码;
调用asPlatformTransactionManager()方法,在该方法中检查以上获取的事务管理器TransactionManager tm应为null,或有实现PlatformTransactionManager接口,不满足时会抛出异常。在示例项目中使用的事务管理器DataSourceTransactionManager有实现PlatformTransactionManager接口,因此当前方法不会抛出异常,可以继续执行;
在示例项目中使用的事务管理器类型为DataSourceTransactionManager,未实现CallbackPreferringPlatformTransactionManager接口,因此会执行满足“txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)”判断条件时才会执行的代码;
调用createTransactionIfNecessary()方法,在该方法中调用prepareTransactionInfo()方法,经过以上处理后,获得TransactionInfo类型的对象txInfo,该对象中的transactionManager成员变量为当前使用的事务管理器;
调用invocation.proceedWithInvocation()方法,执行@Transactional注解所在的需要在事务中执行的自定义方法;
调用自定义方法若出现异常,会调用completeTransactionAfterThrowing()方法,在该方法中调用当前使用的事务管理器的rollback()或commit()方法,进行事务回滚或事务提交,详细说明见后续内容;
调用自定义方法未出现异常时,调用commitTransactionAfterReturning()方法,在该方法中调用txInfo.getTransactionManager().commit()方法,即调用当前使用的事务管理器DataSourceTransactionManager的commit()方法进行事务提交。
以上步骤对应的TransactionAspectSupport.invokeWithinTransaction()方法代码如下:
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final TransactionManager tm = determineTransactionManager(txAttr);
if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
...
}
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
...
}
commitTransactionAfterReturning(txInfo);
return retVal;
}
...
7. 使用事务执行SQL语句详细过程
使用事务执行SQL语句时,可以分为以下三个主要的阶段:
-
从连接池借出连接、关闭自动提交阶段,由Spring完成;
-
执行SQL语句阶段,由MyBatis完成;
-
提交/回滚事务、开启自动提交、归还连接至连接池阶段,由Spring完成。
7.1. 创建连接
使用事务执行SQL语句,在创建连接时,与“Spring、MyBatis、Druid、MySQL不使用事务执行SQL语句分析”中的内容类似,略。
7.2. 执行SQL语句前Spring的处理
使用事务执行SQL语句时,执行SQL语句之前,包含以下操作,通过Spring完成,主要步骤在DataSourceTransactionManager及其父类AbstractPlatformTransactionManager中。
7.2.1. 从连接池借出连接
- 使用@Transactional注解的处理
使用@Transactional注解时,从项目代码开始,到Spring的AbstractPlatformTransactionManager.getTransaction()方法,调用堆栈如下:
test.db.transaction.normal.commit.transaction_manager.TestNormalCommit_Ds1Mapper_Ds1TM.doTest(TestNormalCommit_Ds1Mapper_Ds1TM:15)
test.db.transaction.service.TransactionService$$EnhancerBySpringCGLIB$$26f4c143.runByTransactionalDs1TM(<generated>)
org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy:708)
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy:763)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation:186)
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor:119)
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport:382)
org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport:595)
org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager:373)
从以上调用堆栈可以看到,执行过程经过了Spring AOP增强的类(EnhancerBySpringCGLIB),也经过了以上描述的TransactionAspectSupport.invokeWithinTransaction()方法。
- 使用TransactionTemplate的处理
使用TransactionTemplate时,从项目代码开始,到AbstractPlatformTransactionManager.getTransaction()方法,调用堆栈如下:
test.db.transaction.service.TransactionService.runByTransactionTemplate(TransactionService:79)
org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate:137)
org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager:373)
从以上调用堆栈可以看到,执行过程中不需要经过Spring AOP处理,在调用TransactionTemplate.execute()方法后,就调用了AbstractPlatformTransactionManager.getTransaction()方法方法。
- 后续步骤相同的处理
使用@Transactional注解或TransactionTemplate时,后续的步骤相同,从AbstractPlatformTransactionManager.getTransaction()方法开始,到Druid从连接池借出连接,调用堆栈如下:
org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager:373)
org.springframework.transaction.support.AbstractPlatformTransactionManager.startTransaction(AbstractPlatformTransactionManager:400)
org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager:265)
com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource:100)
com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource:1399)
com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource:1407)
com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl:5055)
test.db.druid_filter.DruidMonitorFilter.dataSource_getConnection(DruidMonitorFilter:59)
com.alibaba.druid.filter.FilterAdapter.dataSource_getConnection(FilterAdapter:2756)
com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl:5059)
com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource:1429)
com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource:1783)
com.alibaba.druid.pool.DruidPooledConnection.<init>(DruidPooledConnection:79)
7.2.2. 关闭自动提交
使用事务执行SQL语句时,在从连接池借出连接后,需要关闭自动提交,使当前会话开启事务。
从AbstractPlatformTransactionManager.startTransaction()方法,经过Druid,到MySQL Connector通过NativeProtocol.sendQueryPacket()方法向MySQL发送SQL语句,调用堆栈如下:
org.springframework.transaction.support.AbstractPlatformTransactionManager.startTransaction(AbstractPlatformTransactionManager:400)
org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager:287)
com.alibaba.druid.pool.DruidPooledConnection.setAutoCommit(DruidPooledConnection:740)
com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.setAutoCommit(ConnectionProxyImpl:429)
com.alibaba.druid.filter.FilterChainImpl.connection_setAutoCommit(FilterChainImpl:733)
test.db.druid_filter.DruidMonitorFilter.connection_setAutoCommit(DruidMonitorFilter:259)
com.alibaba.druid.filter.FilterAdapter.connection_setAutoCommit(FilterAdapter:986)
com.alibaba.druid.filter.FilterChainImpl.connection_setAutoCommit(FilterChainImpl:738)
com.mysql.cj.jdbc.ConnectionImpl.setAutoCommit(ConnectionImpl:2033)
com.mysql.cj.NativeSession.execSQL(NativeSession:663)
com.mysql.cj.protocol.a.NativeProtocol.sendQueryString(NativeProtocol:997)
com.mysql.cj.protocol.a.NativeProtocol.sendQueryPacket(NativeProtocol:1051)
在MySQL Connector的ConnectionImpl.setAutoCommit()方法中,可能执行“SET autocommit=1”或“SET autocommit=0”语句。在关闭自动提交时,执行的SQL语句为“SET autocommit=0”。
从MySQL Connector的NativeProtocol.sendQueryPacket()方法,到通过输出流向MySQL服务发送SQL语句,调用堆栈如下:
com.mysql.cj.protocol.a.NativeProtocol.sendQueryPacket(NativeProtocol:1051)
com.mysql.cj.protocol.a.NativeProtocol.sendCommand(NativeProtocol:683)
com.mysql.cj.protocol.a.NativeProtocol.send(NativeProtocol:619)
com.mysql.cj.protocol.a.TimeTrackingPacketSender.send(TimeTrackingPacketSender:50)
com.mysql.cj.protocol.a.SimplePacketSender.send(SimplePacketSender:51)
java.io.FilterOutputStream.write(FilterOutputStream:97)
java.io.BufferedOutputStream.write(BufferedOutputStream:117)
7.2.3. 在ThreadLocal中记录ConnectionHolder
在关闭自动提交之后,执行SQL语句之前,需要在ThreadLocal中记录org.springframework.jdbc.datasource.ConnectionHolder,在该类中保存了数据库连接对象,在后续执行SQL语句时需要使用。
在Spring的DataSourceTransactionManager.doBegin()方法中,会调用TransactionSynchronizationManager.bindResource()方法,在ThreadLocal的Map中记录信息,传入的key类型为javax.sql.DataSource(使用Druid时为com.alibaba.druid.pool.DruidDataSource),value为ConnectionHolder。
7.3. 执行SQL语句时MyBatis的处理
使用事务时,以下执行SQL语句的操作通过MyBatis完成。
7.3.1. 从调用MyBatis的Mapper接口到SimpleExecutor
使用事务执行SQL语句,从调用MyBatis的Mapper接口到SimpleExecutor,与“Spring、MyBatis、Druid、MySQL不使用事务执行SQL语句分析”中的内容类似,略。
7.3.2. 执行第一条SQL语句
在事务中执行第一条SQL语句与执行非第一条SQL语句相比,获取数据库连接的步骤存在区别。
7.3.2.1. 从ThreadLocal中获取ConnectionHolder
在事务中执行第一条SQL语句时,需要从ThreadLocal中获取ConnectionHolder,以获得数据库连接对象。
执行查询操作时,从MyBatis的SimpleExecutor.doQuery()方法开始,到Spring的TransactionSynchronizationManager.doGetResource()方法,调用堆栈如下:
org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor:59)
org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor:72)
org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor:279)
org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(SpringManagedTransaction:67)
org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction:81)
org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils:80)
org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils:105)
org.springframework.transaction.support.TransactionSynchronizationManager.getResource(TransactionSynchronizationManager:135)
org.springframework.transaction.support.TransactionSynchronizationManager.doGetResource(TransactionSynchronizationManager:157)
执行更新操作时,SimpleExecutor.doUpdate()方法也会调用prepareStatement()方法,后续调用堆栈相同。
在事务中执行第一条SQL语句时,执行到SpringManagedTransaction类的getConnection()方法时,成员变量Connection connection为null,需要调用openConnection()方法打开连接;
SpringManagedTransaction类的相关方法代码如下:
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
后续经过Spring的DataSourceUtils类,到TransactionSynchronizationManager.doGetResource()方法,从ThreadLocal中根据DataSource获取对应的ConnectionHolder。
7.3.2.2. 在SpringManagedTransaction中记录Connection
如以上所示的SpringManagedTransaction.openConnection()方法的代码,在调用DataSourceUtils.getConnection()方法获取到ConnectionHolder后,会将其记录在成员变量Connection connection中。
7.3.2.3. 执行SQL语句
使用事务执行SQL语句时,与“Spring、MyBatis、Druid、MySQL不使用事务执行SQL语句分析”中的内容类似,略。
7.3.3. 执行非第一条SQL语句
7.3.3.1. 从SpringManagedTransaction中获取Connection
在事务中执行非第一条SQL语句时,需要从SpringManagedTransaction中获取Connection,以获得数据库连接。
执行更新操作时,从MyBatis的SimpleExecutor.doUpdate()方法开始,到SpringManagedTransaction.getConnection()方法,调用堆栈如下:
org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor:47)
org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor:72)
org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor:279)
org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(SpringManagedTransaction:69)
执行查询操作时,SimpleExecutor.doQuery()方法也会调用prepareStatement()方法,后续调用堆栈相同。
如以上所示的SpringManagedTransaction.getConnection()方法的代码,在事务中执行非第一条SQL语句时,成员变量Connection connection非null,会直接返回其值,不会调用openConnection()方法。
7.3.3.2. 执行SQL语句
略。
7.4. 执行SQL语句后Spring的处理
使用事务执行SQL语句时,执行SQL语句之后,以下操作通过Spring完成,主要步骤在AbstractPlatformTransactionManager.commit()/rollback()方法中,分别对应提交事务/回滚事务的场景。
7.4.1. 提交事务
- 使用@Transactional注解的处理
使用@Transactional注解时,从项目代码开始,到AbstractPlatformTransactionManager.commit()方法,调用堆栈如下:
test.db.transaction.normal.commit.transaction_manager.TestNormalCommit_Ds1Mapper_Ds1TM.doTest(TestNormalCommit_Ds1Mapper_Ds1TM:15)
test.db.transaction.service.TransactionService$$EnhancerBySpringCGLIB$$26f4c143.runByTransactionalDs1TM(<generated>)
org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy:708)
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy:763)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation:186)
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor:119)
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport:407)
org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport:654)
org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager:711)
- 使用TransactionTemplate的处理
使用TransactionTemplate时,从项目代码开始,到AbstractPlatformTransactionManager.commit()方法,调用堆栈如下:
test.db.transaction.service.TransactionService.runByTransactionTemplate(TransactionService:79)
org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate:152)
org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager:711)
- 后续步骤相同的处理
使用@Transactional注解或TransactionTemplate时,后续的步骤相同,从AbstractPlatformTransactionManager.commit()方法开始,经过Druid,到MySQL Connector向MySQL发送SQL语句,调用堆栈如下:
org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager:711)
org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager:743)
org.springframework.jdbc.datasource.DataSourceTransactionManager.doCommit(DataSourceTransactionManager:333)
com.alibaba.druid.pool.DruidPooledConnection.commit(DruidPooledConnection:782)
com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.commit(ConnectionProxyImpl:122)
com.alibaba.druid.filter.FilterChainImpl.connection_commit(FilterChainImpl:194)
test.db.druid_filter.DruidMonitorFilter.connection_commit(DruidMonitorFilter:198)
com.alibaba.druid.filter.FilterAdapter.connection_commit(FilterAdapter:782)
com.alibaba.druid.filter.FilterChainImpl.connection_commit(FilterChainImpl:199)
com.mysql.cj.jdbc.ConnectionImpl.commit(ConnectionImpl:794)
com.mysql.cj.NativeSession.execSQL(NativeSession:663)
com.mysql.cj.protocol.a.NativeProtocol.sendQueryString(NativeProtocol:997)
com.mysql.cj.protocol.a.NativeProtocol.sendQueryPacket(NativeProtocol:1051)
在MySQL Connector的ConnectionImpl.commit()方法中,会执行“commit”语句,用于提交事务。
7.4.2. 回滚事务
- 使用@Transactional注解的处理
使用@Transactional注解时,从项目代码开始,到AbstractPlatformTransactionManager.rollback()方法,调用堆栈如下:
test.db.transaction.normal.rollback.transaction_manager.TestNormalRollback_Ds1Mapper_Ds1TM.lambda$doTest$0(TestNormalRollback_Ds1Mapper_Ds1TM:16)
test.db.transaction.service.TransactionService$$EnhancerBySpringCGLIB$$26f4c143.runByTransactionalDs1TM(<generated>)
org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy:708)
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy:763)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation:186)
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor:119)
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport:392)
org.springframework.transaction.interceptor.TransactionAspectSupport.completeTransactionAfterThrowing(TransactionAspectSupport:672)
org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(AbstractPlatformTransactionManager:809)
在TransactionAspectSupport.completeTransactionAfterThrowing()方法中,判断以下两个条件是否都满足:
a. 参数中的TransactionInfo txInfo对象的transactionAttribute非空;
b. txInfo.transactionAttribute.rollbackOn(ex)方法返回值为true。
当以上判断条件都满足时,调用txInfo.getTransactionManager().rollback()方法,通过当前使用的事务管理器的rollback()方法回滚事务;
当以上判断条件存在不满足时,调用txInfo.getTransactionManager().commit()方法,通过当前使用的事务管理器的commit()方法提交事务。
以上rollbackOn()方法用于判断出现异常时是否需要使事务回滚,当@Transactional注解的rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName属性与异常对象ex的类型匹配时,代表需要回滚事务;
若以上不匹配,再通过RuleBasedTransactionAttribute的父类DefaultTransactionAttribute的rollbackOn()方法,判断异常对象是否满足RuntimeException、Error类型,若满足则代表需要回滚事务;
若以上都不满足,则代表不回滚事务。
根据以上内容可知,通过@Transactional注解使用事务时,若出现异常,对于需要回滚的异常类型,会使事务回滚;对于其他异常类型,事务会提交。
TransactionAspectSupport.completeTransactionAfterThrowing()方法的代码如下:
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (...) {
...
}
}
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
- 使用TransactionTemplate的处理
使用TransactionTemplate时,从项目代码开始,到AbstractPlatformTransactionManager.rollback()方法,调用堆栈如下:
test.db.transaction.service.TransactionService.runByTransactionTemplate(TransactionService:79)
org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate:144)
org.springframework.transaction.support.TransactionTemplate.rollbackOnException(TransactionTemplate:168)
org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(AbstractPlatformTransactionManager:809)
在Spring的TransactionTemplate.execute()方法中,在调用TransactionCallback<T> action参数的doInTransaction()方法,执行需要在事务中执行的自定义方法时,会进行异常处理:
当捕获到满足RuntimeException或Error类型的异常时,调用rollbackOnException()方法进行回滚处理;
当捕获到其他类型的异常时,在调用rollbackOnException()方法进行回滚处理后,还会抛出UndeclaredThrowableException异常。
TransactionTemplate.execute()方法的代码如下:
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
- 后续步骤相同的处理
使用@Transactional注解或TransactionTemplate时,后续的步骤相同,从AbstractPlatformTransactionManager.rollback()方法开始,到MySQL Connector向MySQL发送SQL语句,调用堆栈如下:
org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(AbstractPlatformTransactionManager:809)
org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager:835)
org.springframework.jdbc.datasource.DataSourceTransactionManager.doRollback(DataSourceTransactionManager:348)
com.alibaba.druid.pool.DruidPooledConnection.rollback(DruidPooledConnection:814)
com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.rollback(ConnectionProxyImpl:399)
com.alibaba.druid.filter.FilterChainImpl.connection_rollback(FilterChainImpl:709)
test.db.druid_filter.DruidMonitorFilter.connection_rollback(DruidMonitorFilter:216)
com.alibaba.druid.filter.FilterAdapter.connection_rollback(FilterAdapter:974)
com.alibaba.druid.filter.FilterChainImpl.connection_rollback(FilterChainImpl:714)
com.mysql.cj.jdbc.ConnectionImpl.rollback(ConnectionImpl:1824)
com.mysql.cj.jdbc.ConnectionImpl.rollbackNoChecks(ConnectionImpl:1927)
com.mysql.cj.NativeSession.execSQL(NativeSession:663)
com.mysql.cj.protocol.a.NativeProtocol.sendQueryString(NativeProtocol:997)
com.mysql.cj.protocol.a.NativeProtocol.sendQueryPacket(NativeProtocol:1051)
com.mysql.cj.protocol.a.NativeProtocol.sendCommand(NativeProtocol:635)
在MySQL Connector的ConnectionImpl.rollbackNoChecks()方法中,会执行“rollback”语句,用于回滚事务。
7.4.3. 清理ThreadLocal中的ConnectionHolder
在提交事务或回滚事务时,都会清理ThreadLocal中的ConnectionHolder,提交事务与回滚事务分别调用AbstractPlatformTransactionManager类的commit()/rollback()方法,后续都会调用到cleanupAfterCompletion()方法。
从AbstractPlatformTransactionManager类的commit()方法到cleanupAfterCompletion()方法的调用堆栈如下:
org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager:711)
org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager:790)
org.springframework.transaction.support.AbstractPlatformTransactionManager.cleanupAfterCompletion(AbstractPlatformTransactionManager:992)
从AbstractPlatformTransactionManager类的rollback()方法到cleanupAfterCompletion()方法的调用堆栈如下:
org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(AbstractPlatformTransactionManager:809)
org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager:875)
org.springframework.transaction.support.AbstractPlatformTransactionManager.cleanupAfterCompletion(AbstractPlatformTransactionManager:992)
从AbstractPlatformTransactionManager.cleanupAfterCompletion()方法到TransactionSynchronizationManager.unbindResource()方法的调用堆栈如下:
org.springframework.transaction.support.AbstractPlatformTransactionManager.cleanupAfterCompletion(AbstractPlatformTransactionManager:992)
org.springframework.jdbc.datasource.DataSourceTransactionManager.doCleanupAfterCompletion(DataSourceTransactionManager:371)
org.springframework.transaction.support.TransactionSynchronizationManager.unbindResource(TransactionSynchronizationManager:197)
在调用TransactionSynchronizationManager.unbindResource()方法时,传入参数key的类型为com.alibaba.druid.pool.DruidDataSource,会将ThreadLocal的Map中以上key对应的键值对清理,对应的value类型为ConnectionHolder。
7.4.4. 开启自动提交
在归还连接至连接池之前,需要开启自动提交,用于将当前会话的系统变量autocommit恢复默认值。
从AbstractPlatformTransactionManager.cleanupAfterCompletion()方法开始,经过Druid,到MySQL Connector向MySQL发送SQL语句,调用堆栈如下:
org.springframework.transaction.support.AbstractPlatformTransactionManager.cleanupAfterCompletion(AbstractPlatformTransactionManager:992)
org.springframework.jdbc.datasource.DataSourceTransactionManager.doCleanupAfterCompletion(DataSourceTransactionManager:378)
com.alibaba.druid.pool.DruidPooledConnection.setAutoCommit(DruidPooledConnection:740)
com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.setAutoCommit(ConnectionProxyImpl:429)
com.alibaba.druid.filter.FilterChainImpl.connection_setAutoCommit(FilterChainImpl:733)
test.db.druid_filter.DruidMonitorFilter.connection_setAutoCommit(DruidMonitorFilter:259)
com.alibaba.druid.filter.FilterAdapter.connection_setAutoCommit(FilterAdapter:986)
com.alibaba.druid.filter.FilterChainImpl.connection_setAutoCommit(FilterChainImpl:738)
com.mysql.cj.jdbc.ConnectionImpl.setAutoCommit(ConnectionImpl:2033)
com.mysql.cj.NativeSession.execSQL(NativeSession:663)
com.mysql.cj.protocol.a.NativeProtocol.sendQueryString(NativeProtocol:997)
com.mysql.cj.protocol.a.NativeProtocol.sendQueryPacket(NativeProtocol:1051)
在MySQL Connector的ConnectionImpl.setAutoCommit()方法中,在开启自动提交时,会执行“SET autocommit=1”语句。
7.4.5. 归还连接至连接池
从AbstractPlatformTransactionManager.cleanupAfterCompletion()方法开始,到Druid归还连接至连接池,调用堆栈如下:
org.springframework.transaction.support.AbstractPlatformTransactionManager.cleanupAfterCompletion(AbstractPlatformTransactionManager:992)
org.springframework.jdbc.datasource.DataSourceTransactionManager.doCleanupAfterCompletion(DataSourceTransactionManager:391)
org.springframework.jdbc.datasource.DataSourceUtils.releaseConnection(DataSourceUtils:360)
org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils:393)
org.springframework.jdbc.datasource.DataSourceUtils.doCloseConnection(DataSourceUtils:406)
com.alibaba.druid.pool.DruidPooledConnection.close(DruidPooledConnection:286)
com.alibaba.druid.filter.FilterChainImpl.dataSource_recycle(FilterChainImpl:5045)
test.db.druid_filter.DruidMonitorFilter.dataSource_releaseConnection(DruidMonitorFilter:119)
com.alibaba.druid.filter.FilterAdapter.dataSource_releaseConnection(FilterAdapter:2750)
com.alibaba.druid.filter.FilterChainImpl.dataSource_recycle(FilterChainImpl:5049)
com.alibaba.druid.pool.DruidPooledConnection.recycle(DruidPooledConnection:351)
com.alibaba.druid.pool.DruidDataSource.recycle(DruidDataSource:1951)
com.alibaba.druid.pool.DruidConnectionHolder.reset(DruidConnectionHolder:296)
7.5. 关闭连接
使用事务执行SQL语句,在关闭连接时,与“Spring、MyBatis、Druid、MySQL不使用事务执行SQL语句分析”中的内容类似,略。