文章目录
事务的传播(transaction propagation)
假设我们想要在一个事务A内要调用另一个事务B,考虑以下几个场景要如何实现,
- 内层事务B失败回滚,外层事务A也一起回滚
- 内层事务B失败回滚,外层事务A不回滚正常提交
- 内层事务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不为空,则恢复被挂起的事务。