【Java】Spring的声明事务在多线程场景中失效问题。
大家都知道Spring的声明式事务在多线程当中会失效,来看一下如下案例。
按照如下方式,b()方法抛出异常,由于父子线程导致事务失效,a()会成功插入,但是b()不会。
因此成功插入一条数据,事务失效。
@Component
public class UserServiceImpl implements UserService{@Transactionalpublic void a(){jdbcTemplate.execute("insert into `user`(`age`,`name`,`city`) values(18,'张三','北京')");UserService userService = (UserService)AopContext.currentProxy();Thread thread = new Thread(()->{userService.b();})}@Transactionalpublic void b(){jdbcTemplate.execute("insert into `user`(`age`,`name`,`city`) values(19,'张三','北京')");throw new RuntimeException();}}
这里就需要了解一下嵌套方法事务的传播行为是怎么实现的?
如图所示,如果b()方法是在子线程当中的,因为ThreadLocal不是同一个因此子线程又创建了一个事务。由于是各用各的事务所以事务b就会回滚,而事务a的数据就会插入成功。
要保证这种父子线程中的事务传播,则在创建子线程后把父线程中的事务取出来再设置进去。
进行如下改造后保证两个线程使用同一个事务。
那么接下来的问题就是如何获取外层的connection
以及如何设置到子线程的ThreadLocal中。
这里就需要看一下源码了
需要看这个类org.springframework.jdbc.datasource.DataSourceTransactionManager
找到doBegin()
方法
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;Connection con = null;try {if (!txObject.hasConnectionHolder() ||txObject.getConnectionHolder().isSynchronizedWithTransaction()) {Connection newCon = obtainDataSource().getConnection();if (logger.isDebugEnabled()) {logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");}txObject.setConnectionHolder(new ConnectionHolder(newCon), true);}txObject.getConnectionHolder().setSynchronizedWithTransaction(true);con = txObject.getConnectionHolder().getConnection();Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);txObject.setPreviousIsolationLevel(previousIsolationLevel);txObject.setReadOnly(definition.isReadOnly());if (con.getAutoCommit()) {txObject.setMustRestoreAutoCommit(true);if (logger.isDebugEnabled()) {logger.debug("Switching JDBC Connection [" + con + "] to manual commit");}// 这里开启事务con.setAutoCommit(false);}prepareTransactionalConnection(con, definition);txObject.getConnectionHolder().setTransactionActive(true);int timeout = determineTimeout(definition);if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {txObject.getConnectionHolder().setTimeoutInSeconds(timeout);}// 这里将连接存入到ThreadLocalif (txObject.isNewConnectionHolder()) {TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());}}catch (Throwable ex) {if (txObject.isNewConnectionHolder()) {DataSourceUtils.releaseConnection(con, obtainDataSource());txObject.setConnectionHolder(null, false);}throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);}
}
最终改造
@Component
public class UserServiceImpl implements UserService{@Transactionalpublic void a(){ConnectionHolder connectionHolder = TransactionSynchronizationManager.getResource(dataSource);jdbcTemplate.execute("insert into `user`(`age`,`name`,`city`) values(18,'张三','北京')");UserService userService = (UserService)AopContext.currentProxy();Thread thread = new Thread(()->{// 绑定主线程的connection到子线程TransactionSynchronizationManager.bindResource(dataSource,connectionHolder);userService.b();})}@Transactionalpublic void b(){jdbcTemplate.execute("insert into `user`(`age`,`name`,`city`) values(19,'张三','北京')");throw new RuntimeException();}}