Propagation是@Transactional注解的参数,定义了Spring在执行事务方法时处理事务的策略。有一下几个枚举值:
- REQUIRED
方法需要在事务中执行,如果已经有事务,则在此事务中执行,否则,新建事务 - SUPPORTS
方法支持在事务中执行,但是如果现在没有事务,则以非事务的方式执行 - MANDATORY
方法需要在事务中执行,若没有事务,则抛异常 - REQUIRES_NEW
方法需要在新事务中执行,若当前没有事务,则开启事务执行;若已有事务,则将它挂起,开启新事务执行 - NOT_SUPPORTED
方法不支持事务,若当前有事务,则将它挂起,以非事务的方式运行 - NEVER
方法不支持事务,若当前有事务,则抛出异常 - NESTED
方法支持嵌套事务,若当前有事务,则以嵌套事务的方式执行,否则,开启事务执行
Demo测试
其中SUPPORTS、MANDATORY、NOT_SUPPORTED、NEVER最多只有一个事务在执行,也比较好理解,就不多研究了。
REQUIRED、REQUIRES_NEW、NESTED有点不好理解,主要是“挂起”、“嵌套”这些术语的意义不是很清楚。
下面先通过一个demo来测试下这几个传播级别的差别,对内外层方法对影响。
首先定义了两个service,外层的UserService会执行向db插入一条数据的操作,然后会调用内层的CommonService,CommonService中方法模拟异常抛出回滚。
接口:
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public interface IUserService {
String saveUser(User user);
}
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public interface ICommonService {
void testException();
}
实现:
@Service
public class UserService implements IUserService {
@Autowired
UserDao mUserDao;
@Autowired
ICommonService mCommonService;
@Override
public String saveUser(User user) {
mUserDao.insertUser(user);
try {
mCommonService.testException();
} catch (Exception e) {
e.printStackTrace();
}
return "Success";
}
}
@Service
public class CommonService implements ICommonService {
@Override
public void testException() {
mCommonDao.updateRecord();
int i = 1/0;
}
}
外层将传播级别固定设为Propagation.REQUIRED,以开启事务,内层方法的传播级别分别设为Propagation.REQUIRED、REQUIRES_NEW、NESTED来进行测试。
- 当内层方法设置为Propagation.REQUIRED时,发现执行失败,抛异常:
org.springframework.transaction.UnexpectedRollbackException:
Transaction rolled back because it has been marked as rollback-only
外层事务回滚了,数据插入失败。虽然内层的异常在外层被捕获了,但是由于内层也经过了事务逻辑的增强,在异常的时候设置了回滚,在外层事务逻辑提交的时候,发现同一个事务已经被标记为“rollback-only”,所以也只能抛出异常了。 - 当内层方法设置为Propagation. REQUIRES_NEW时,发现执行成功,外层数据插入逻辑成功,内层方法更新失败,互相没有影响,说明是在两个事务中执行的。
- 当内层方法设置为Propagation. NESTED时,发现也是执行成功,外层数据插入逻辑成功,内层方法更新失败,和Propagation. REQUIRES_NEW的现象一样,那么它们有什么差别呢。
前面说到了挂起、嵌套,Propagation. REQUIRES_NEW应该是挂起了之前的事务,新创建了事务,而Propagation. NESTED是一个嵌套的事务。刚看到这些术语的时候我以为是rdbs中的定义,后来查询半天并没有找到,想到应该是spring中做的处理,于是研究下源码。
源码分析
我们知道spring中声明式事务的切面逻辑是在TransactionInterceptor中执行的,终点看下这个类里面的源码在已有事务的情况下,子方法设置为REQUIRED、REQUIRES_NEW、NESTED的处理逻辑:
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
跟进代码,发现创建事务的逻辑在
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(def, transaction, debugEnabled);
}
若当前已有事务,则执行handleExistingTransaction的逻辑
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);
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException | Error beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
}
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException(
"Transaction manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
}
if (debugEnabled) {
logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
}
if (useSavepointForNestedTransaction()) {
// Create savepoint within existing Spring-managed transaction,
// through the SavepointManager API implemented by TransactionStatus.
// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
status.createAndHoldSavepoint();
return status;
}
else {
// Nested transaction through nested begin and commit/rollback calls.
// Usually only for JTA: Spring synchronization might get activated here
// in case of a pre-existing JTA transaction.
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, null);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
}
可以看到,处理PROPAGATION_NESTED和PROPAGATION_REQUIRES_NEW处理的不同,PROPAGATION_NESTED是suspend了老事务,重新开启了新事务,而PROPAGATION_NESTED是保存点savepoint,这下比较清楚了,嵌套事务是通过savepoint来实现的,外层事务设置保存点,内存方法回滚的时候是回滚到外层的保存点,外层事务的执行不受影响,但是由于还是一个事务,所以事务的提交肯定是由外层来进行的,所以外层事务异常回滚的话,内存逻辑肯定也会回滚。可以修改demo测试一下,将异常代码移到外层逻辑,发现果然如此。而PROPAGATION_REQUIRES_NEW却不受影响,可见果然是两个分开的事务,那么是怎么实现的呢?
查看suspend和doBegin的代码
protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
List suspendedSynchronizations = doSuspendSynchronization();
try {
Object suspendedResources = null;
if (transaction != null) {
suspendedResources = doSuspend(transaction);
}
String name = TransactionSynchronizationManager.getCurrentTransactionName();
TransactionSynchronizationManager.setCurrentTransactionName(null);
boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
TransactionSynchronizationManager.setActualTransactionActive(false);
return new SuspendedResourcesHolder(
suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
}
catch (RuntimeException | Error ex) {
// doSuspend failed - original transaction is still active...
doResumeSynchronization(suspendedSynchronizations);
throw ex;
}
}
else if (transaction != null) {
// Transaction active but no synchronization active.
Object suspendedResources = doSuspend(transaction);
return new SuspendedResourcesHolder(suspendedResources);
}
else {
// Neither transaction nor synchronization active.
return null;
}
}
发现在挂起事务时,将当前的事务信息从TransactionSynchronizationManager中移除了,同时清除的还有transaction中的连接信息,这样在再次开始事务时获取到的是一个新的连接
@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);
}
内外层执行用的都不是一个数据库连接,那自然也就不是一个事务了。
总结:spring中事务的挂起、嵌套不是数据库系统中定义的概念,事务的挂起其实就是将原连接进行保存,用新的连接去处理内层事务,处理完成再用保存的连接继续执行之前的事务;而嵌套事务对应的是数据库系统的savepoint,嵌套一个内事务时时间上是设置了一个保存点,内存逻辑回滚只回滚到保存点,不影响外层逻辑,由于还是一个事务,所以外层逻辑如果回滚了,内层逻辑依然会被回滚。如果需要两个事务逻辑互不影响,请使用REQUIRES_NEW。