Spring Transaction Propagation 原理 [II]

事务的传播(transaction propagation)

假设我们想要在一个事务A内要调用另一个事务B,考虑以下几个场景要如何实现,

  1. 内层事务B失败回滚,外层事务A也一起回滚
  2. 内层事务B失败回滚,外层事务A不回滚正常提交
  3. 内层事务B提交后,外层事务A异常回滚,要求事务B也一起回滚

按照惯例,我们一起看下不使用Spring以上场景怎么实现,首先我们先定义几个公用的方法

public T doTransaction(Supplier<T> supplier) {
    connection.beginTransaction();
    try {
        T result = supplier.get();
        connection.commit();
        return result;
    } catch(Exception e) {
        connection.rollback();
        throw e;
    } 
}
private abstract Object insertFirstPeople() {...}
private abstract Object insertSecondPeople() {...}

doTransaction()是一个模板方法,封装事务处理逻辑。

场景1——内层事务B失败回滚,外层事务A也一起回滚

private Object insertFirstPeople() {
	doTransaction(new Supplier<Object>() {
		People people = new People();
		people.setName("Lily");
		people.setAge(18);
		repository.save(people);
		
		insertSecondPeople();
	});
}
private Object insertSecondPeople() {
	doTransaction(new Supplier<Object>() {
		People people = new People();
		people.setName("jhon");
		people.setAge(23);
		repository.save(people)
	
		if(new Random().nextBoolean()) {
			throw new RuntimeException("DummyException: this should cause rollback of both inserts!");
  		}
	});
}

insertSecondPeople()异常时会发生回滚,同时会把异常向上抛给insertFirstPeople(),这样就可以实现场景1的需求了。

场景2——内层事务B失败回滚,外层事务A不回滚正常提交

private Object insertFirstPeople() {
	doTransaction(new Supplier<Object>() {
		People people = new People();
		people.setName("Lily");
		people.setAge(18);
		repository.save(people);
		
		try {
			insertSecondPeople();
		}  catch (RuntimeException e) {
			System.err.println("Exception: " + e);
		}
	});
}

在insertFirstPeople()捕获insertSecondPeople()方法的异常即可。

场景3——内层事务B提交后,外层事务A异常回滚,要求事务B也一起回滚

原则上一个事务一旦被提交是不可以再回滚的,所以在该场景下要确保事务A、B要么一起成功,要么一起失败,我们继续修改上面两个方法。

首先,我们增加一个ThreadLocal的变量用来记录当前是否已经在事务中;然后,根据变量的状态决定是否要开启一个新事务,为满足上述场景,默认如果在事务中就不再创建新事务。

方案1——放到一个事务内

private ThreadLocal<Boolean> isInTransaction = ThreadLocal.withInitial(() -> false);
private Object insertFirstPeople() {
	doTransaction(new Supplier<Object>() {
		People people = new People();
		people.setName("Lily");
		people.setAge(18);
		repository.save(people);
		
		// 标记开启事务
		isInTransaction.set(True);
		insertSecondPeopleWithoutTransaction();
		
		if(new Random().nextBoolean()) {
			throw new RuntimeException("DummyException: this should cause rollback of both inserts!");
  		}
	});
}
// 如果已经在事务中,则加入当前事务;否则开启新事务
private Object insertSecondPeople() {
	if (isInTransaction.get()) {
		return insertSecondPeopleWithoutTransaction();
	} else {
		return doTransaction(this::insertSecondPeopleWithoutTransaction);
	}
}
// 删除事务逻辑
private Object insertSecondPeopleWithoutTransaction() {
	People people = new People();
	people.setName("jhon");
	people.setAge(23);
	repository.save(people)	
}

方案2——内嵌事务保存savePoint

本质上也是放到同一个事务内

private ThreadLocal<Boolean> isInTransaction = ThreadLocal.withInitial(() -> false);
public T doSavepoint(Supplier<T> supplier) {
    connection.setSavepoint();
    try {
        T result = supplier.get();
        connection.releaseSavepoint(savepoint);
        return result;
    } catch(Exception e) {
        connection.rollback(savepoint);
        throw e;
    } 
}
private Object insertFirstPeople() {
	doTransaction(new Supplier<Object>() {
		People people = new People();
		people.setName("Lily");
		people.setAge(18);
		repository.save(people);
		
		// 标记开启事务
		isInTransaction.set(True);
		insertSecondPeople();
	
		if(new Random().nextBoolean()) {
			throw new RuntimeException("DummyException: this should cause rollback of both inserts!");
  		}
	});
}
// 如果在事务中,记录savepoint;否则,开启新事务
private Object insertSecondPeople() {
	if (isInTransaction.get()) {
		return doSavepoint(this::insertSecondPeopleWithoutTransaction);
	} else {
		return doTransaction(this::insertSecondPeopleWithoutTransaction);
	}
}

通过上述三个场景不断地修改insertFirstPeople()和insertSecondPeople()方法,基本已经形成了事务传播的基本解决思路。

更进一步,为insertSecondPeople()方法定义一个事务传播的描述符,我们可以将其中的if-else逻辑放到doTransaction()模板方法中,根据事务传播描述决定是加入当前事务、创建一个新事务、savepoint、抛出异常等。下面是伪代码,

private ThreadLocal<Boolean> isInTransaction = ThreadLocal.withInitial(() -> false);

public T doTransaction(Supplier<T> supplier) {
	Connection connection = null;
	// 获取当前方法描述信息,这里通过注解或反射来实现
	Propagation propagation = getMethodDescription(supplier);
	if (propagation == Propagation.Never) {
		if (isInTransaction.get()) {
			throw new RuntimeException("DummyException: method does not support transaction"); // 如果存在事务,抛出不支持事务异常
		} else {
			return supplier.get(); // 没有事务,则按非事务方式执行
		}	
	} else if (propagation == Propagation.REQUIRED) {
		if (isInTransaction.get()) {
			connection = getCurrentConnection(); // 如果已存在事务,则加入
		} else {
			connection = getNewConnectiong(); // 否则,就新开启一个事务
		}	
	} else {
		connection = getNewConnectiong();
	}
    
    connection.beginTransaction();
    try {
        T result = supplier.get();
        connection.commit();
        return result;
    } catch(Exception e) {
        connection.rollback();
        throw e;
    } 
}

private Object insertFirstPeople() {
	doTransaction(@Descriptor("Propagation.REQUIRED") new Supplier<Object>() {
		...
		insertSecondPeople();
	});
}
private Object insertSecondPeople() {
	doTransaction(@Descriptor("Propagation.REQUIRED") new Supplier<Object>() {
		...
	});
}

上面代码中实现了两类事务传播特性。如,

  • Propagation.Never;以非事务方式执行,如果当前存在事务,则抛出异常。
  • Propagation.REQUIRED;如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。

当然实际应用中事务传播特性还有很多,事务的传播特性与隔离性一样都属于事务定义范畴。

Spring事务的传播的抽象(transaction propagation abstraction)

Spring明确定义了7种事务传播类型的行为。

事务传播行为类型说明
PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。[默认]
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起(暂停)。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

我们再回到TransactionTemplate模板方法

public <T> T execute(TransactionCallback<T> action) throws TransactionException {
	assert transactionManager != null;
	
	TransactionStatus status = this.transactionManager.getTransaction(this);
	T result;
	try {
		// 执行业务逻辑
		result = action.doInTransaction();
	}
	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;
}

开启事务

TransactionStatus status = this.transactionManager.getTransaction(this);

TransactionTemplate实现了TransactionDefinition,上面this参数传入的就是事务的定义。还有两个关键的类TransactionStatus和transactionManager,我们分别看下。

TransactionStatus——事务状态,以默认实现DefaultTransactionStatus为例,引用属性保存了事务对象(connection)、savepoint、当前挂起的事务(恢复时要从status中获取被‘挂起’的事务),状态属性保存了是否完成、是否仅回滚等。

TransactionManager是如何通过TransactionStatus来开启事务的?

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
	// 1.获取当前事务对象
	Object transaction = doGetTransaction();
	
	// 2.判断是否已存在事务
	if (isExistingTransaction(transaction)) {
		// Existing transaction found -> check propagation behavior to find out how to behave.
		return handleExistingTransaction(definition, transaction, debugEnabled);
	}

	// No existing transaction found -> check propagation behavior to find out how to proceed.
	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
		throw new IllegalTransactionStateException(
				"No existing transaction found for transaction marked with propagation 'mandatory'");
	}
	else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
			definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
			definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
		// 3.挂起指定事务,当前没有事务传空即可。当无事务时不作任何处理,当前场景下忽略即可。
		SuspendedResourcesHolder suspendedResources = suspend(null);
		// 创建TransactionStatus
		DefaultTransactionStatus status = newTransactionStatus(...);
		// 4.开启事务
		doBegin(transaction, definition);
		// 初始化事务同步事件,先忽略
		prepareSynchronization(status, definition);
		return status;
	}
	else {
		// 默认事务传播属性为PROPAGATION_REQUIRED,如果事务传播类型不在上述条件内,则返回一个空事务,先忽略
		// @See PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER
	}
}

简单情况:当前不存在事务

1,doGetTransaction(),获取一个事务对象,主要工作是从ThreadLocal中获取一个ConnectionHolder的对象,没有的话connectionHolder属性为null,这里属性值一定是null。
在这里插入图片描述
2,transaction对象中已经保存了从ThreadLocal中获取到的ConnectionHolder对象,只是判断这个对象是否为空就可以判断当前是否已存在事务,当前也不存在事务,跳过if逻辑。

3,挂起指定事务,当前没有事务传空即可。如果当前不存在任何事务,该方法不会作任何处理,直接返回null。

4,开启事务,通过DataSource获取connection对象,为connection对象设置定义的隔离级别,原隔离级别保存到transaction对象中,并关闭connection的自动提交;并在ThreadLocal中记录当前数据源和connection对象。

复杂情况:当前已存在事务

private TransactionStatus handleExistingTransaction(
			TransactionDefinition definition, Object transaction, boolean debugEnabled)
			throws TransactionException {
	// 事务传播
	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
		throw new IllegalTransactionStateException(
				"Existing transaction found for transaction marked with propagation 'never'");
	}
	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
		// 挂起事务
		Object suspendedResources = suspend(transaction);
		// 返回TransactionStatus,其中事务对象为null
	}
	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
		// 挂起事务
		Object suspendedResources = suspend(transaction);
		// 创建一个新事务,重新获取connection对象,开启新事务
	}
	if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
		assert isNestedTransactionAllowed() == true;
		if (useSavepointForNestedTransaction()) {
			// 创建savepoint
		} 
		else {
			// Usually only for JTA
			// 创建新事务
		}
	}

	// 其它事务传播类型,复用当前事务即可
	return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}

如果判断当前存在同一事务,根据事务传播属性,有以下几种情况,

  • 不支持在事务内运行,抛出异常
  • 以非事务运行,挂起当前事务,返回一个空事务的TransactionStatus,以非事务运行
  • 新建事务执行,挂起当前事务,创建新事务执行
  • 内嵌事务,无须挂起事务,创建savepoint

执行事务

try {
	result = action.doInTransaction(status);
}

这里很简单,没什么可讲的。

提交事务

this.transactionManager.commit(status);

1,执行提交前的一些触发逻辑
2,如果存在savepoint,释放,然后返回;否则,到第3步
3,如果存在事务,进行提交
4,执行提交后的触发逻辑
5,清理事务管理器,也就是ThreadLocal的内容,如果TransactionStatus对象中的SuspendedResources不为空,则恢复被挂起的事务。

回滚事务

1,执行提交前的一些触发逻辑
2,如果存在savepoint,则回滚到savepoint;否则,到第3步
3,如果存在事务,则回滚事务
4,执行回滚后的触发逻辑
5,清理事务管理器,也就是ThreadLocal的内容,如果TransactionStatus对象中的SuspendedResources不为空,则恢复被挂起的事务。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值