事务未能正确提交导致Druid报错connection holder is null(2)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 事务未能正确提交导致Druid报错connection holder is null(2)

3.4. MySQL事务隐式提交的场景

参考“Transaction Life Cycle”MySQL: Welcome。


在以下情况下,事务会被提交:


当用户执行COMMIT语句时;


MySQL事务隐式提交,当MySQL服务开始处理DDL或SET AUTOCOMMIT={0|1}语句时。


关于会导致事务隐式提交的SQL语句,在“Statements That Cause an Implicit Commit”MySQL :: MySQL 5.6 Reference Manual :: 13.3.3 Statements That Cause an Implicit Commit中有详细说明,包括DDL等。


在以上事务开启后未提交/回滚的场景下,对应的连接不会被其他线程再获取到,因此也不会触发MySQL事务隐式提交


3.5. 事务隐式回滚的场景

3.5.1. MySQL事务隐式回滚

除了在MySQL客户端执行ROLLBACK语句进行显式回滚外,以下情况下MySQL服务也会进行隐式回滚。


3.5.1.1. 连接断开与事务回滚


参考“LOCK TABLES and UNLOCK TABLES Statements”MySQL :: MySQL 5.6 Reference Manual :: 13.3.5 LOCK TABLES and UNLOCK TABLES Statements。


当一个客户端会话的连接断开时,假如存在活动的事务,则MySQL服务会将事务回滚。


3.5.1.2. 连接超时与事务回滚


参考MySQL :: MySQL 5.6 Reference Manual :: 5.1.7 Server System Variables。


MySQL系统变量wait_timeout用于设置MySQL服务在关闭非交互式连接之前等待其活动的时间,单位为秒。默认值为28800,即8小时。可能需要同时修改全局系统变量wait_timeout与interactive_timeout,才能使会话系统wait_timeout的修改生效。


即MySQL连接不活动超过该时间后,MySQL服务会将该连接断开,对应的事务也会被回滚。


3.5.1.3. 行锁超时与事务回滚


参考“InnoDB Error Handling”MySQL :: MySQL 5.6 Reference Manual :: 14.21.4 InnoDB Error Handling,MySQL :: MySQL 5.6 Reference Manual :: 14.14 InnoDB Startup Options and System Variables。


InnoDB在等待获取行锁时,假如超过了一定的时间,则会进行回滚。


系统变量innodb_lock_wait_timeout用于设置以上获取行锁超时时间,单位为秒,默认为50。


当系统变量innodb_rollback_on_timeout为假时,InnoDB会将当前语句(即等待行锁并导致超时的语句)进行回滚(此时整个事务还是活动状态);当该系统变量为真时,InnoDB会将整个事务进行回滚。


该变量值默认值为假,即默认情况下,事务中执行的语句获取行锁超时,对应语句会被InnoDB回滚。


3.5.1.4. 死锁与事务回滚


参考“Deadlock Detection”MySQL :: MySQL 5.6 Reference Manual :: 14.7.5.2 Deadlock Detection,“InnoDB Error Handling”MySQL :: MySQL 5.6 Reference Manual :: 14.21.4 InnoDB Error Handling。


InnoDB会自动检测事务死锁,当出现死锁时,会将一个或多个事务回滚以打破死锁。InnoDB尝试将“小”的事务回滚,事务的大小由插入、更新或删除的行数决定。


3.5.1.5. InnoDB其他错误与事务回滚


参考“InnoDB Error Handling”MySQL :: MySQL 5.6 Reference Manual :: 14.21.4 InnoDB Error Handling。


假如在SQL语句中没有指定IGNORE,则出现重复键错误时,InnoDB会将对应的SQL语句回滚;


出现行太长错误时,InnoDB会将对应的SQL语句回滚;


其他错误大多由InnoDB存储引擎层之上的MySQL服务层检测,并将对应的SQL语句回滚。


3.5.2. Druid事务隐式回滚

MySQL connector中,用于回滚事务的方法为com.mysql.cj.jdbc.ConnectionImpl类,rollback()方法


在Druid中,com.alibaba.druid.pool.DruidPooledConnection类,rollback()方法,会调用以上方法,即调用Druid的回滚事务方法时,Druid会执行MySQL connector中回滚事务的方法


除此之外,com.alibaba.druid.pool.DruidDataSource类,recycle()方法中,也会调用以上方法,相关代码如下:

final boolean isAutoCommit = holder.underlyingAutoCommit;
final boolean isReadOnly = holder.underlyingReadOnly;
try {
    // check need to rollback?
    if ((!isAutoCommit) && (!isReadOnly)) {
        pooledConnection.rollback();
    }

recycle()方法在归还数据库连接至连接池时会被执行,isAutoCommit代表当前连接是否启用autocommit(未开启事务),isReadOnly代表数据库是否只读


以上执行回滚的场景,是归还数据库连接至Druid连接池时,假如有开启事务,且数据库非只读,则会执行事务回滚操作

3.6. 分析不同线程是否使用相同数据库连接的方式

在Java应用中访问MySQL服务时,涉及Java应用、网络传输、MySQL服务这三层,在每一层都可以对执行的SQL语句与事务操作进行监控与观测


3.6.1. 数据库层

可在MySQL数据库中通过一般查询日志分析对应的SQL使用哪个线程/连接执行


可参考“MySQL SQL语句与事务执行及日志分析”MySQL SQL语句与事务执行及日志分析_mysql根据事务id查询执行的sql_adrninistrat0r的博客-CSDN博客


需要DBA开启对应的日志,且一般查询日志的数据量太大,因此该方法不可行


3.6.2. 网络层

可对Java应用与MySQL服务器之间的数据进行抓包分析,检查执行SQL语句时使用的本地端口(可反映对应哪个连接)


可参考“tcpdump、Wireshark抓包分析MySQL SQL语句与事务执行”tcpdump、Wireshark抓包分析MySQL SQL语句与事务执行_tcpdump 抓包mysql_adrninistrat0r的博客-CSDN博客


需要有服务器root权限才能执行相关命令,执行不方便


3.6.3. Java应用层

在Java应用层分析执行SQL语句时使用的连接是比较方便的


可参考“Spring、MyBatis、Druid、MySQL执行SQL语句与事务监控” Spring、MyBatis、Druid、MySQL执行SQL语句与事务监控_监控 mybatis 生成 sql 语句_adrninistrat0r的博客-CSDN博客


为了分析事务开启后未提交/回滚时,相关线程后续是否使用原有连接继续执行sql语句,可以通过以下方式实现:


观察数据库操作各个重要节点的情况

使用Druid提供的Filter:stat、log4j2,内容略


确认使用事务执行sql语句时是否使用新连接

在spring-jdbc的org.springframework.jdbc.datasource.DataSourceTransactionManager类中,doBegin()方法会执行开启事务的操作


在以上方法中,若当前事务没有已存在的数据库连接,需要从数据库连接池中获取连接时,会在日志中打印DEBUG级别的日志“Acquired Connection … for JDBC transaction”


确认不使用事务执行sql语句时是否使用新连接

在spring-jdbc的org.springframework.jdbc.datasource.DataSourceUtils类中,doGetConnection()方法会执行获取数据库连接的操作


在以上方法中,若没有使用当前线程ThreadLocal中对应的数据库连接,而是从数据库连接池中获取连接时,会在日志中打印DEBUG级别的日志“Fetching JDBC Connection from DataSource”


确认现有事务是否被暂停或恢复

当前线程已存在对应的事务时,使用新事务(REQUIRES_NEW),或不使用事务(NOT_SUPPORTED)执行sql语句时,会对现有事务执行暂停及恢复,可通过以下方式观察日志


在spring-tx的org.springframework.transaction.support.AbstractPlatformTransactionManager类中,handleExistingTransaction()方法用于处理已存在的事务


在以上方法中,若需要暂停当前线程,会在日志中打印DEBUG级别的日志“Suspending current transaction”


cleanupAfterCompletion()方法用于在事务提交/回滚后进行清理操作


在以上方法中,若发现之前的事务被暂停后需要恢复,会在日志中打印DEBUG级别的日志“Resuming suspended transaction after completion of inner transaction”


3.7. Spring提供的主要的事务使用方式

@Transactional注解

属于声明式事务,某些场景下无法实现编程式事务的效果


事务模板

属于编程式事务,主要是使用TransactionTemplate类


事务管理器

属于编程式事务,主要是使用PlatformTransactionManager接口的实例


以上接口提供了三个方法,分别用于开启事务、提交事务、回滚事务,由于开启事务与提交/回滚事务是需要分别调用的,因此有可能出现漏调用的情况


4. 细节分析

4.1. 为什么不使用事务时会使用ThreadLocal中的数据库连接

开启事务后未提交/回滚,后续使用线程池中原有的线程,执行数据库操作时不使用事务,会使用原有的连接,原因如下:


不使用事务执行数据库操作时,会调用org.springframework.jdbc.datasource.DataSourceUtils类的doGetConnection()方法获取连接,从MyBatis的Mapper接口对应类开始,到以上方法的调用堆栈如下:

com.sun.proxy.$Proxy37.updateByPrimaryKeySelective(Unknown Source)
org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy:86)
org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy:145)
org.apache.ibatis.binding.MapperMethod.execute(MapperMethod:67)
org.mybatis.spring.SqlSessionTemplate.update(SqlSessionTemplate:288)
com.sun.proxy.$Proxy35.update(Unknown Source)
org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate:427)
java.lang.reflect.Method.invoke(Method:498)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl:43)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl:62)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession:194)
org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor:76)
org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor:117)
org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor:49)
org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor:86)
org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor:337)
org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(SpringManagedTransaction:67)
org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction:80)
org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils:80)
org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils:112)

以上方法的相关代码如下:

ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
    conHolder.requested();
    if (!conHolder.hasConnection()) {
        logger.debug("Fetching resumed JDBC Connection from DataSource");
        conHolder.setConnection(fetchConnection(dataSource));
    }
    return conHolder.getConnection();
}

在调用TransactionSynchronizationManager.getResource()方法获取ConnectionHolder类型的对象conHolder后,会判断conHolder是否满足非空,且hasConnection()或isSynchronizedWithTransaction()方法返回值为真,若满足则返回conHolder.getConnection()方法的返回值,即使用以上获取的连接


在org.springframework.transaction.support.TransactionSynchronizationManager类的getResource()方法中,会调用doGetResource()方法,在该方法中会从ThreadLocal类型的resources字段中获取对应的连接对象,resources字段及相关代码如下:

private static final ThreadLocal<Map<Object, Object>> resources =
        new NamedThreadLocal<>("Transactional resources");
...
Map<Object, Object> map = resources.get();
if (map == null) {
    return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
    map.remove(actualKey);
    // Remove entire ThreadLocal if empty...
    if (map.isEmpty()) {
        resources.remove();
    }
    value = null;
}
return value;

通过以上代码可知,不使用事务执行数据库操作时,若当前线程的ThreadLocal中的resources存在对应的连接时,会使用对应的连接执行数据库操作


当使用事务执行数据库操作时,会在ThreadLocal中设置以上信息,具体过程见后续内容


4.2. 为什么事务不提交/回滚时ThreadLocal会保持

在事务执行的正常流程中,开启事务时在ThreadLocal记录对应的连接信息,提交/回滚事务时进行清理


若开启事务后不执行提交/回滚事务的操作,则ThreadLocal中的连接信息会保持,直到对应的线程被线程池回收,或应用退出时被清理


4.3. 为什么使用事务时可能使用ThreadLocal中的数据库连接

4.3.1. 开启事务获取数据库连接阶段

org.springframework.transaction.support.AbstractPlatformTransactionManager类的getTransaction()方法用于开启事务,在该方法中,首先调用doGetTransaction()方法获取当前存在的事务


在以上方法中,会调用TransactionSynchronizationManager.getResource()方法,获取ThreadLocal中的数据库连接(前文有分析)


后续会根据Spring事务传播行为进行处理,部分事务传播行为会使用现有的事务执行后续的数据库操作


4.3.2. 执行sql语句阶段

与前文“为什么不使用事务时会使用ThreadLocal中的数据库连接”原因相同,略


4.4. 为什么不同Spring事务传播行为使用的连接不同

AbstractPlatformTransactionManager类用于开启事务的getTransaction()方法中,判断是否已存在事务及相关的处理代码如下:

if (isExistingTransaction(transaction)) {
    // Existing transaction found -> check propagation behavior to find out how to behave.
    return handleExistingTransaction(def, transaction, debugEnabled);
}

isExistingTransaction()方法用于判断当前是否已存在事务,项目中实际使用的事务管理器类型为子类org.springframework.jdbc.datasource.DataSourceTransactionManager,该类的isExistingTransaction()方法代码如下:

DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());


开启事务后未提交/回滚事务,后续使用原有线程执行时,以上txObject对象的hasConnectionHolder()及getConnectionHolder().isTransactionActive()方法均返回真,因此会执行handleExistingTransaction()方法


handleExistingTransaction()方法用于在存在事务时进行处理,定义在AbstractPlatformTransactionManager类中,部分代码如下,可以看到有根据事务传播行为进行对应的处理


部分事务传播行为会调用startTransaction()方法时,会创建新的事务,即需要从数据库连接池获取可用的连接


部分事务传播行为会调用prepareTransactionStatus()方法时,若传入的参数2 transaction为null,则代表不使用事务执行数据库操作;若非null,则代表使用指定的当前事务执行数据库操作


4.5. 为什么部分Spring事务传播行为使用原有连接时不会提交事务

若开启事务后不执行提交/回滚事务的操作,后续使用原有线程执行,新事务使用的事务传播行为是REQUIRED、SUPPORTS、MANDATORY、NESTED时,新的事务也不会提交,原因如下:


AbstractPlatformTransactionManager.commit()方法用于提交事务,该方法会调用processCommit()方法


在processCommit()方法中,仅当当前事务为新事务时,才会执行实际提交事务的doCommit()方法,相关代码如下:


else if (status.isNewTransaction()) {
    if (status.isDebug()) {
        logger.debug("Initiating transaction commit");
    }
    unexpectedRollback = status.isGlobalRollbackOnly();
    doCommit(status);
}

以上isNewTransaction()方法在org.springframework.transaction.support.DefaultTransactionStatus类中,当其transaction字段非null,且newTransaction字段为真时,isNewTransaction()方法会返回真


DefaultTransactionStatus的newTransaction字段只能在构造函数中赋值


AbstractPlatformTransactionManager.newTransactionStatus()方法中会创建DefaultTransactionStatus对象


以上方法只有以下两种调用情况:


在startTransaction()方法中调用newTransactionStatus()方法,传入的参数3 newTransaction为true

在prepareTransactionStatus()方法中调用newTransactionStatus()方法,传入的参数3 newTransaction为prepareTransactionStatus()方法的参数3newTransaction

根据上一部分拷贝的Spring代码可知,当Spring事务传播行为是REQUIRES_NEW时,会调用startTransaction()方法,即newTransaction为true,最终可以提交事务


Spring事务传播行为是其他值时,会调用prepareTransactionStatus()方法,且参数3 newTransaction为false,最终不会提交事务


4.6. 为什么使用事务传播行为REQUIRES_NEW之后ThreadLocal中的连接不会被修改

4.6.1. 假设

事务开启后不提交/回滚,对应线程的ThreadLocal中就保留了对应的数据库连接信息


原有线程后续使用事务执行数据库操作,Spring事务传播行为使用REQUIRES_NEW,假如使用新的事务执行数据库操作后,会将ThreadLocal中的数据库连接信息清空,则当前线程就能够恢复正常,之后执行的数据库操作能够正常提交


(不能将ThreadLocal中保留为新事务对应的数据库连接信息,否则还是有类似的问题)


4.6.2. 分析

以上假设不满足,不符合Spring事务传播行为的设计


Spring的TransactionDefinition类中对REQUIRES_NEW事务传播行为的说明如下:


Create a new transaction, suspending the current transaction if one exists.

使用REQUIRES_NEW事务传播行为时,会创建新的事务,假如存在当前事务则暂停当前事务,当前事务还会在随后被恢复


暂停当前事务

AbstractPlatformTransactionManager.handleExistingTransaction()用于对现有事务进行处理,若事务传播行为是NOT_SUPPORTED、REQUIRES_NEW时,会调用suspend()方法暂停当前事务,相关代码如下:

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
    if (debugEnabled) {
        logger.debug("Suspending current transaction");
    }
    Object suspendedResources = suspend(transaction);
}
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
    if (debugEnabled) {
        logger.debug("Suspending current transaction, creating new transaction with name [" +
                definition.getName() + "]");
    }
    SuspendedResourcesHolder suspendedResources = suspend(transaction);
}

suspend()方法中调用doSuspend()方法执行暂停当前事务的操作,并将当前事务的相关资源保存在suspendedResources对象中,相关代码如下:

Object suspendedResources = doSuspend(transaction);
return new SuspendedResourcesHolder(suspendedResources);

以上方法调用的是子类org.springframework.jdbc.datasource.DataSourceTransactionManager的doSuspend()方法,该方法中会调用TransactionSynchronizationManager.unbindResource()方法将ThreadLocal中当前事务的数据库连接信息清空,相关代码如下:

DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
txObject.setConnectionHolder(null);
return TransactionSynchronizationManager.unbindResource(obtainDataSource());

执行到此后,当前线程ThreadLocal中的连接信息已被清空


后续会将新创建事务的连接信息记录在ThreadLocal中


恢复当前事务

AbstractPlatformTransactionManager类中,执行提交事务的方法为processCommit(),执行回滚事务的方法为processRollback(),以上两个方法都会在最后finally中调用cleanupAfterCompletion()方法,在事务结束后执行清理操作


在cleanupAfterCompletion()方法中,假如DefaultTransactionStatus类型的status对象的getSuspendedResources()方法返回值非空,即存在被暂停的事务相关资源时,会调用resume()方法对被暂停的事务进行恢复,相关代码如下:

if (status.getSuspendedResources() != null) {
    if (status.isDebug()) {
        logger.debug("Resuming suspended transaction after completion of inner transaction");
    }
    Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
    resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
}

在resume()方法中会调用doResume()方法,对应子类DataSourceTransactionManager的doResume()方法,在该方法中会调用TransactionSynchronizationManager.bindResource()方法,将原有事务的数据库连接信息恢复到ThreadLocal中


4.7. 为什么Druid数据源中活跃连接的事务不能被其他线程提交

4.7.1. 假设

假如Druid数据源会将活跃连接放回连接池中,则后续有其他线程获取到原有的数据库连接时,可以将之前的会话提交


4.7.2. 分析

以上假设不成立,原因如下


Druid不会自动提交事务

MySQL connector中,用于提交事务的方法为com.mysql.cj.jdbc.ConnectionImpl类,commit()方法


以上方法在com.alibaba.druid.pool.DruidPooledConnection类,commit()方法中被调用,即只有在调用Druid的提交事务方法时,Druid才会执行MySQL connector中提交事务的方法


即Druid不会自动提交事务


Druid不会将活跃连接放回连接池

未发现Druid将活跃连接放回连接池的相关参数配置及代码


Druid归还连接时会对事务隐式回滚

Druid将数据库连接归还到连接池时,会对事务隐式回滚,说明见前文


即使Druid会将活跃连接放回连接池,也会将对应的事务回滚,后续无法再提交


4.8. 为什么事务开启后不提交/回滚时后续不会被提交

事务开启后不提交/回滚,则对应的数据库连接在Druid数据源中会处于活动状态,无法被其他线程获取到,因此无法被其他线程提交


当前线程的ThreadLocal中有记录对应的数据库连接信息,假如当前线程后续又执行了其他数据库操作,分以下情况考虑


不使用事务

当前线程后续不使用事务,执行其他数据库操作时,会使用原有的连接,但因为不使用事务,不会执行commit,即不会提交原有事务


使用事务,使用原有连接

当前线程后续使用事务,使用原有连接(对应REQUIRED等Spring事务传播行为),执行其他数据库操作时,因为此时事务不属于新事务,在尝试提交事务的过程中,不会实际执行提交事务的操作


使用事务,使用新的连接

当前线程后续使用事务,使用新的连接(对应REQUIRES_NEW等Spring事务传播行为),会使用新的连接提交事务,与原有事务不属于同一个数据库连接,不会提交原有事务


根据以上内容可知,事务开启后不提交/回滚,不管是其他线程还是原有线程,后续都不会提交原有事务


以上情况可总结为如下表格:

image.png


4.9. 为什么事务开启后不提交/回滚时最终会被回滚

结合前文可知,经过一段时间后,在以下情况下,原有的事务会被回滚


当Java应用进程结束时,会关闭所有的数据库连接,原有的事务会回滚

当MySQL服务器kill对应的线程时,也会关闭对应的数据库连接,原有的事务会回滚

当事务超时(默认8小时),或在事务中获取行锁超时(默认50秒)时,原有的事务会回滚

因此事务开启后不提交/回滚时,最终会被回滚


4.10. Spring事务传播行为对事务嵌套执行的影响

以下分析Spring事务传播行为对事务嵌套执行的影响,即一个线程已开启一个事务后,又尝试开启事务的情况


4.10.1. 使用现有事务

一个线程已开启一个事务后,又使用事务传播行为REQUIRED开启事务,情况如下:


第二个(及之后)事务开启时,不会开启新的事务,实际上还是使用现有事务;


第二个(及之后)事务执行完毕进行提交时,不会提交现有事务;


第一个事务执行完毕进行提交时,会对事务执行提交


即:使用现有的事务时,由第一次开启事务的代码最后对事务执行提交,后续的事务(没有开启新事务)不会对事务执行提交


以上流程如下所示:

bde06a57f5784475bf80b731080c1fdb.png



4.10.2. 使用新的事务

一个线程已开启一个事务后,又使用事务传播行为REQUIRES_NEW开启事务,情况如下:


第二个(及之后)事务开启时,会开启新的事务;


第二个(及之后)事务执行完毕进行提交时,只提交当前开启的事务,不会提交原有事务;


第一个事务执行完毕进行提交时,会对原有事务执行提交


即:使用新的事务时,每一次事务的开启后都由当前事务进行提交,不会提交原有事务


以上流程如下所示:




5. 编程式事务的建议使用方式

需要使用编程式事务时,建议使用Spring事务模板TransactionTemplate


5.1. Spring事务模板怎样保证事务提交/回滚

org.springframework.transaction.support.TransactionTemplate类的execute()方法用于通过事务执行数据库操作


setPropagationBehavior()方法可以用于设置TransactionTemplate对应的Spring事务传播行为


execute()方法部分代码如下:

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;

以上action为需要在事务中执行的自定义代码


在执行过程中出现异常时,会调用rollbackOnException()方法对事务进行回滚


若自定义代码执行完毕且未出现异常,则会调用transactionManager.commit()方法对事务进行提交


Spring事务模板会在数据库操作正常执行结束后提交事务,出现异常时回滚事务,不会出现遗漏



相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2月前
【bug记录】旋转链表与力扣报错:member access within null pointer of type ‘struct ListNode‘
【bug记录】旋转链表与力扣报错:member access within null pointer of type ‘struct ListNode‘
|
2月前
解决微软云Azure Function运行报错-Value cannot be null. (Parameter ‘provider‘)
解决微软云Azure Function运行报错-Value cannot be null. (Parameter ‘provider‘)
50 4
|
2月前
|
JavaScript 前端开发 C++
【Azure Function】调试 VS Code Javascript Function本地不能运行,报错 Value cannot be null. (Parameter 'provider')问题
【Azure Function】调试 VS Code Javascript Function本地不能运行,报错 Value cannot be null. (Parameter 'provider')问题
|
4月前
|
Java Spring
解决Springboot集成ElasticSearch 报错:A bean with that name has already been defined in null and overriding
解决Springboot集成ElasticSearch 报错:A bean with that name has already been defined in null and overriding
115 2
|
4月前
|
Web App开发 分布式计算 大数据
MaxCompute操作报错合集之配置归并节点,出现java.lang.NullPointerException: null错误提示,该怎么办
MaxCompute是阿里云提供的大规模离线数据处理服务,用于大数据分析、挖掘和报表生成等场景。在使用MaxCompute进行数据处理时,可能会遇到各种操作报错。以下是一些常见的MaxCompute操作报错及其可能的原因与解决措施的合集。
|
5月前
|
SQL 关系型数据库 MySQL
实时计算 Flink版产品使用合集之从MySQL同步数据到Doris时,历史数据时间字段显示为null,而增量数据部分的时间类型字段正常显示的原因是什么
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStreamAPI、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
2月前
|
SQL 关系型数据库 MySQL
在 MySQL 中使用 IS NULL
【8月更文挑战第12天】
591 0
在 MySQL 中使用 IS NULL
|
5月前
|
SQL 关系型数据库 MySQL
python在mysql中插入或者更新null空值
这段代码是Python操作MySQL数据库的示例。它执行SQL查询从表`a_kuakao_school`中选取`id`,`university_id`和`grade`,当`university_id`大于0时按升序排列。然后遍历结果,根据`row[4]`的值决定`grade`是否为`NULL`。若不为空,`grade`被格式化为字符串;否则,设为`NULL`。接着构造UPDATE语句更新`university`表中对应`id`的`grade`值,并提交事务。重要的是,字符串`NULL`不应加引号,否则更新会失败。
130 2
|
2月前
|
SQL 关系型数据库 MySQL
mysql不等于<>取特定值反向条件的时候字段有null值或空值读取不到数据
对于数据库开发的专业人士来说,理解NULL的特性并知道如何正确地在查询中处理它们是非常重要的。以上所介绍的技巧和实例可以帮助你更精准地执行数据库查询,并确保数据的完整性和准确性。在编写代码和设计数据库结构时,牢记这些细节将有助于你避免许多常见的错误,提高数据库应用的质量与性能。
50 0
|
3月前
|
SQL 存储 索引
MySQL设计规约问题之为什么应该把字段定义为NOT NULL并且提供默认值
MySQL设计规约问题之为什么应该把字段定义为NOT NULL并且提供默认值
下一篇
无影云桌面