大家都知道Spring的声明式事务在多线程当中会失效,来看一下如下案例。
按照如下方式,b()方法抛出异常,由于父子线程导致事务失效,a()会成功插入,但是b()不会。
因此成功插入一条数据,事务失效。
@Component
public class UserServiceImpl implements UserService{
@Transactional
public void a(){
jdbcTemplate.execute("insert into `user`(`age`,`name`,`city`) values(18,'张三','北京')");
UserService userService = (UserService)AopContext.currentProxy();
Thread thread = new Thread(()->{
userService.b();
})
}
@Transactional
public 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);
}
// 这里将连接存入到ThreadLocal
if (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{
@Transactional
public 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();
})
}
@Transactional
public void b(){
jdbcTemplate.execute("insert into `user`(`age`,`name`,`city`) values(19,'张三','北京')");
throw new RuntimeException();
}
}