Spring、MyBatis、Druid、MySQL使用事务执行SQL语句分析

25 篇文章 0 订阅

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语句分析”中的内容类似,略。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是一个用于创建独立的、基于生产级别的Spring应用程序的框架。它简化了Spring应用程序的配置和部署过程,并提供了许多开箱即用的功能和插件,使开发人员能够更快地构建应用程序。 MyBatis是一个开源的持久层框架,它通过XML或注解的方式将Java对象映射到数据库中的SQL语句。它提供了灵活的SQL映射配置和强大的动态SQL支持,使得开发人员能够更方便地进行数据库操作。 Druid是一个开源的数据库连接池,它提供了高性能、可扩展和可监控的数据库连接池解决方案。它具有强大的监控和统计功能,可以帮助开发人员更好地管理和优化数据库连接。 在Spring Boot中使用MyBatisDruid的步骤如下: 1. 添加相关依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> ``` 2. 配置数据库连接信息: 在application.yml文件中添加以下配置: ```yaml spring: datasource: url: jdbc:mysql://localhost:3306/mydatabase username: root password: password driver-class-name: com.mysql.jdbc.Driver ``` 3. 编写MyBatis的Mapper接口和SQL映射文件: 创建Mapper接口,并使用@Mapper注解标识: ```java @Mapper public interface UserMapper { List<User> getAllUsers(); void insertUser(User user); void updateUser(User user); void deleteUser(int id); } ``` 创建SQL映射文件,例如UserMapper.xml: ```xml <mapper namespace="com.example.mapper.UserMapper"> <select id="getAllUsers" resultType="com.example.model.User"> SELECT * FROM users </select> <insert id="insertUser" parameterType="com.example.model.User"> INSERT INTO users (name, age) VALUES (#{name}, #{age}) </insert> <update id="updateUser" parameterType="com.example.model.User"> UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id} </update> <delete id="deleteUser" parameterType="int"> DELETE FROM users WHERE id = #{id} </delete> </mapper> ``` 4. 编写Service和Controller: 创建Service接口和实现类,用于处理业务逻辑: ```java public interface UserService { List<User> getAllUsers(); void insertUser(User user); void updateUser(User user); void deleteUser(int id); } @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public List<User> getAllUsers() { return userMapper.getAllUsers(); } @Override public void insertUser(User user) { userMapper.insertUser(user); } @Override public void updateUser(User user) { userMapper.updateUser(user); } @Override public void deleteUser(int id) { userMapper.deleteUser(id); } } @RestController public class UserController { @Autowired private UserService userService; @GetMapping("/users") public List<User> getAllUsers() { return userService.getAllUsers(); } @PostMapping("/users") public void insertUser(@RequestBody User user) { userService.insertUser(user); } @PutMapping("/users/{id}") public void updateUser(@PathVariable int id, @RequestBody User user) { user.setId(id); userService.updateUser(user); } @DeleteMapping("/users/{id}") public void deleteUser(@PathVariable int id) { userService.deleteUser(id); } } ``` 以上是使用Spring Boot、MyBatisDruid的基本配置和使用方法。通过以上步骤,你可以在Spring Boot应用程序中使用MyBatisDruid进行数据库操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值