Spring5源码之Spring七种传播特性的详解

2023-06-28 14:33:01 浏览数 (1)

七种事务传播特性:

本篇文章主要讲解Spring事务的传播属性,先看一下下表:

传播特性名称

PROPAGATION_REQUIRED

如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中

PROPAGATION_SUPPORTS

支持使用当前事务,如果当前事务不存在,则不使用事务。** 解释:** 如果ServiceA.method开了事务,那么ServiceB就将自己加入ServiceA中来运行,如果ServiceA.method没有开事务,那么ServiceB自己也不开事务

PROPAGATION_MANDATORY

必须被一个开启了事务的方法来调用自己,否则报错

PROPAGATION_REQUIRES_NEW

创建一个新事务,如果当前事务存在,把当前事务挂起。解释: ServiceB.method强制性自己开启一个新的事务,然后ServiceA.method的事务会卡住,等ServiceB事务完了自己再继续。这就是影响的回滚了,如果ServiceA报错了,ServiceB是不会受到影响的,ServiceB报错了,ServiceA也可以选择性的回滚或者是提交。

PROPAGATION_NOT_SUPPORTED

无事务执行,如果当前事务存在,把当前事务挂起。解释: 就是ServiceB.method不支持事务,ServiceA的事务执行到ServiceB那儿,就挂起来了,ServiceB用非事务方式运行结束,ServiceA事务再继续运行。这个好处就是ServiceB代码报错不会让ServiceA回滚。

PROPAGATION_NEVER

无事务执行,如果当前有事务则抛出Exception。解释: 不能被一个事务来调用,ServiceA.method开事务了,但是调用了ServiceB会报错

PROPAGATION_NESTED

嵌套事务,如果当前事务存在,那么在嵌套的事务中执行。如果当前事务不存在,则表现跟REQUIRED一样。解释: 开启嵌套事务,ServiceB开启一个子事务,如果回滚的话,那么ServiceB就回滚到开启子事务的这个save point。

当前不存在事务的情况下

每次创建一个TransactionInfo的时候都会去new一个Transaction,然后去线程变量Map中拿holder,当此时线程变量的Map中holder为空时,就会视为当前情况下不存在事务,所以transaction中holder = null。

1. PROPAGATION_MANDATORY
  • 使用当前事务,如果当前没有事务,则抛出异常 在上一篇文章 Spring事务增强器 二,一篇文章让你彻底搞懂Spring事务 中我们在getTransaction方法中可以看到如下代码:
代码语言:javascript复制
// 走到这里说明此时没有存在事务,如果事务的传播特性是 MANDATORY 则抛出异常
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
    throw new IllegalTransactionStateException(
                        "No existing transaction found for transaction marked with propagation 'mandatory'");
}
2. REQUIRED、REQUIRES_NEW、NESTED

我们继续看上一篇文章中 Spring事务增强器 二,一篇文章让你彻底搞懂Spring事务getTransaction方法:

代码语言:javascript复制
// 如果此时不存在事务,当传播特性是 REQUIRED  REQUIRES_NEW  NESTED 都会进入if语句块
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
                def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
    // PROPAGATION_REQUIRED PROPAGATION_REQUIRES_NEW  PROPAGATION_NESTED 都需要新建事务,、
    // 因为此时不存在事务,将null 挂起
    SuspendedResourcesHolder suspendedResources = suspend(null);
    if (debugEnabled) {
        logger.debug("Creating new transaction with name ["   def.getName()   "]: "   def);
    }
    try {
        // 注意这个方法
        // new 一个status,存放刚刚创建的transaction,然后将其标记为新事务
        // 新开一个连接的地方,非常重要
        return startTransaction(def, transaction, debugEnabled, suspendedResources);
    }
    catch (RuntimeException | Error ex) {
        resume(null, suspendedResources);
        throw ex;
    }
}

startTransaction方法:

代码语言:javascript复制
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
            Boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
    Boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    // new 一个status,存放刚刚创建的transaction,然后将其标记为新事务
    // 这里的 transaction 后面的一个参数决定是否是新事务
    DefaultTransactionStatus status = newTransactionStatus(
                    definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
    // 新开一个连接的地方,非常重要
    doBegin(transaction, definition);
    prepareSynchronization(status, definition);
    return status;
}

此时会将null 挂起,此时的status变量为:

代码语言:javascript复制
DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);

此时的transaction中的holder依然为null,标记为新事务,接着就会执行doBegin方法了:

  • 看源码(DataTransactionManager.java)
代码语言:javascript复制
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    Connection con = null;
    try {
        // 判断如果transaction 没有holder的话,才去dataSource中获取一个新的连接
        if (!txObject.hasConnectionHolder() ||
                            txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            // 通过 dataSource获取
            Connection newCon = obtainDataSource().getConnection();
            if (logger.isDebugEnabled()) {
                logger.debug("Acquired Connection ["   newCon   "] for JDBC transaction");
            }
            // 所以只有transaction的holder为空时,才会设置新的holder
            // 将获取的连接封装进 ConnectionHolder 然后封装进 transaction 的 connectionholder 属性
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }
        // 设置新的连接为事务同步中
        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
        con = txObject.getConnectionHolder().getConnection();
        // con设置事务隔离级别为 只读
        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
        // /DataSourceTransactionObject设置事务隔离级别
        txObject.setPreviousIsolationLevel(previousIsolationLevel);
        txObject.setReadOnly(definition.isReadOnly());
        // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
        // so we don't want to do it unnecessarily (for example if we've explicitly
        // configured the connection pool to set it already).
        // 如果是自动提交切换到手动提交
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            if (logger.isDebugEnabled()) {
                logger.debug("Switching JDBC Connection ["   con   "] to manual commit");
            }
            con.setAutoCommit(false);
        }
        // 如果只读,执行sql设置事务只读
        prepareTransactionalConnection(con, definition);
        //设置connection 持有者的事务开启状态  这里 呼应 isExistingTransaction 方法的 txObject.getConnectionHolder().isTransactionActive())
        txObject.getConnectionHolder().setTransactionActive(true);
        int timeout = determineTimeout(definition);
        if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
            // 设置超时秒数
            txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
        }
        // Bind the connection holder to the thread.
        if (txObject.isNewConnectionHolder()) {
            // 将当前获取到的连接 绑定 到当前线程  绑定与解绑围绕一个线程变量,此变量在TransactionSynchronizationManager类中:
            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);
    }
}

所以,一切都是新的,新的事物,新的holder,新的连接,在当前不存在事务的时候一切都是新创建的。

这三种传播特性在当前不存在事务的情况是没有区别的,此事务都为新创建的连接,在回滚和提交的时候都可以正常回滚或是提交,就像正常的事务操作那样。

代码语言:javascript复制
else {
    // Create "empty" transaction: no actual transaction, but potentially synchronization.
    if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
        logger.warn("Custom isolation level specified but no actual transaction initiated; "  
                                "isolation level will effectively be ignored: "   def);
    }
    // 其它的事务传播特性一律返回一个空事务,transaction=null
    // 当前不存在事务,且传播机制=PROPAGATION_SUPPORTS/PROPAGATION_NOT_SUPPORTED/PROPAGATION_NEVER,这三种情况,创建“空”事务
    Boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
    return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}

我们看到Status中第二个参数传的是null,表示一个空事务,意思是当前线程中并没有Connection,那如何进行数据库的操作呢?上一篇文章 Spring事务增强器 二,一篇文章让你彻底搞懂Spring事务 中我们有一个扩充的知识点,MyBatis中使用的数据库连接是从通过

TransactionSynchronizationManager.getResource(Object key)获取spring增强方法中绑定到线程的connection,如下代码,那当传播属性为PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER这几种时,并没有创建新的Connection,当前线程中也没有绑定Connection,那Mybatis是如何获取Connecion的呢?这里留一个疑问,我们后期看Mybatis的源码的时候来解决这个疑问

代码语言:javascript复制
@Nullable
public Object getResource(Object key) {
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    return doGetResource(actualKey);
}
代码语言:javascript复制
@Nullable
private Object doGetResource(Object actualKey) {
    return this.transactionContext.getResources().get(actualKey);
}

此时我们知道Status中的Transaction为null,在目标方法执行完毕之后,进行回滚或提交,会判断当前事务是否是新事务,代码如下:

代码语言:javascript复制
@Override
public Boolean isNewTransaction() {
    return (hasTransaction() && this.newTransaction);
}

此时transaction为null,回滚或提交的时候将什么也不会做

当前存在事务的情况下:

在我之前的文章 Spring事务增强器 二,一篇文章让你彻底搞懂Spring事务 中已经讲过,第一次事务开始时必会新创一个holder然后做绑定操作,此时线程变量是由holder的且active为true,如果第二个事务进来,去new一个transaction之后去线程变量中取holder,holder是不为空的且active为true,所以会进入handleExistingTransaction方法:

也是看getTransation方法里面的

  • 看源码(AbstractPlatformTransactionManager.java
代码语言:javascript复制
private TransactionStatus handleExistingTransaction(
            TransactionDefinition definition, Object transaction, Boolean debugEnabled)
            throws TransactionException {
    // 1. PROPAGATION_NEVER(不支持当前事务,如果当前事务存在,则抛出异常) 报错
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
        throw new IllegalTransactionStateException(
                            "Existing transaction found for transaction marked with propagation 'never'");
    }
    // PROPAGATION_NOT_SUPPORTED (不支持当前事务,现有同步将挂起),挂起当前事务,返回一个空事务
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
        if (debugEnabled) {
            logger.debug("Suspending current transaction");
        }
        // 这里会将原来的事务挂起,并返回被挂起的对象。
        Object suspendedResources = suspend(transaction);
        Boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        // 这里可以看到,第二个参数transaction传了一个空事务,第三个参数false为旧标记
        // 最后一个参数就是将前面的挂起的对象封装进了一个新的Status中,当前事务执行完成后,就恢复suspendedResources
        return prepareTransactionStatus(
                            definition, null, false, newSynchronization, debugEnabled, suspendedResources);
    }
    // 挂起当前事务,创建新事务
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
        if (debugEnabled) {
            logger.debug("Suspending current transaction, creating new transaction with name ["  
                                    definition.getName()   "]");
        }
        // 将原事务挂起,此时新建事务,不与原事务有关系。
        // 会将transaction 中的holder 设置为 null ,然后解绑
        SuspendedResourcesHolder suspendedResources = suspend(transaction);
        try {
            // 注意这个函数。
            // new一个status出来,传入transaction,并且为新事务标记,然后传入挂起事务
            // 这里也做了一次doBegin,此时的transaction中holer是为空的,因为之前的事务被挂起了
            // 所以这里会取一次新的连接,并且绑定!
            return startTransaction(definition, transaction, debugEnabled, suspendedResources);
        }
        catch (RuntimeException | Error beginEx) {
            resumeAfterBeginException(transaction, suspendedResources, beginEx);
            throw beginEx;
        }
    }
    // 如果此时的传播特性是 PROPAGATION_NESTED,不会挂起事务
    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()   "]");
        }
        // 这里如果是JTA事务管理器,就不可以用savePoint了,将不会进入此方法
        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.
            // 这里不会挂起事务,说明NESTED的特性是原事务的子事务而已
            // new一个status,传入transaction,传入旧事务标记,传入挂起对象=null
            DefaultTransactionStatus status =
                                    prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
            // 这里是NESTED特性特殊的地方,在先前存在事务的情况下会建立一个savePoint
            status.createAndHoldSavepoint();
            return status;
        } else {
            // JTA事务走这个分支,创建新事务
            // 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.
            // JTA事务走这个分支,创建新事务
            return startTransaction(definition, transaction, debugEnabled, null);
        }
    }
    // Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
    if (debugEnabled) {
        logger.debug("Participating in existing transaction");
    }
    if (isValidateExistingTransaction()) {
        if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
            Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
            if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
                Constants isoConstants = DefaultTransactionDefinition.constants;
                throw new IllegalTransactionStateException("Participating transaction with definition ["  
                                            definition   "] specifies isolation level which is incompatible with existing transaction: "  
                                            (currentIsolationLevel != null ?
                                                    isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
                                                    "(unknown)"));
            }
        }
        if (!definition.isReadOnly()) {
            if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                throw new IllegalTransactionStateException("Participating transaction with definition ["  
                                            definition   "] is not marked as read-only but existing transaction is");
            }
        }
    }
    // 到这里PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED或PROPAGATION_MANDATORY,存在事务加入事务即可,标记为旧事务,空挂起
    Boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}
1. NERVER

不支持当前事务;如果当前事务存在,则抛出异常

代码语言:javascript复制
// 1. PROPAGATION_NEVER(不支持当前事务,如果当前事务存在,则抛出异常) 报错
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
    throw new IllegalTransactionStateException(
                        "Existing transaction found for transaction marked with propagation 'never'");
}

我们看到如果当前线程中存在事务,传播属性为PROPAGATION_NERVER,会直接抛出异常

2. NOT_SUPPORTED

以非事务方式运行,如果当前有事务,则挂起当前事务。看如下代码:

代码语言:javascript复制
// PROPAGATION_NOT_SUPPORTED (不支持当前事务,现有同步将挂起),挂起当前事务,返回一个空事务
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
    if (debugEnabled) {
        logger.debug("Suspending current transaction");
    }
    // 这里会将原来的事务挂起,并返回被挂起的对象。
    Object suspendedResources = suspend(transaction);
    Boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
    // 这里可以看到,第二个参数transaction传了一个空事务,第三个参数false为旧标记
    // 最后一个参数就是将前面的挂起的对象封装进了一个新的Status中,当前事务执行完成后,就恢复suspendedResources
    return prepareTransactionStatus(
                        definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}

如果传播属性为PROPAGATION_NOT_SUPPORTED,会将原来的transaction挂起,此时status为:

代码语言:javascript复制
return prepareTransactionStatus(definition, null, false, newSynchronization, debugEnabled, suspendedResources);

transaction为空,旧事务,挂起的对象存入status中。

此时与外层事务隔离了,在这种传播特性下,是不进行事务的,当提交时,因为是旧事务,所以不会commit,失败时也不会回滚rollback

3. REQUIRES_NEW

此时会先挂起,然后去执行doBegin方法,此时会创建一个新的连接,新holder,新holder有什么用呢?

如果是新holder,会在doBegin中做绑定操作,将新holder绑定到当前线程,其次,在提交或是回滚时finally语句块始终会执行清理方法时判断新holder会进行解绑操作。

  • 看源码(DataTransactionManager.java)
代码语言:javascript复制
@Override
protected void doCleanupAfterCompletion(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    // Remove the connection holder from the thread, if exposed.
    if (txObject.isNewConnectionHolder()) {
        // 将数据库连接从当前线程中解除绑定,解绑过程我们在挂起的过程中已经分析过
        TransactionSynchronizationManager.unbindResource(obtainDataSource());
    }
    // Reset connection.
    // 释放连接,当前事务完成,则需要将连接释放,如果有线程池,则重置数据库连接,放回线程池
    Connection con = txObject.getConnectionHolder().getConnection();
    try {
        if (txObject.isMustRestoreAutoCommit()) {
            // 恢复数据库连接的自动提交属性
            con.setAutoCommit(true);
        }
        // 重置数据库连接
        DataSourceUtils.resetConnectionAfterTransaction(
                            con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
    }
    catch (Throwable ex) {
        logger.debug("Could not reset JDBC Connection after transaction", ex);
    }
    if (txObject.isNewConnectionHolder()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Releasing JDBC Connection ["   con   "] after transaction");
        }
        // 如果当前事务是独立的新创建的事务则在事务完成时释放数据库连接
        DataSourceUtils.releaseConnection(con, this.dataSource);
    }
    txObject.getConnectionHolder().clear();
}

符合传播特性,所以这里REQUIRES_NEW这个传播特性是与原事务相隔离的,用的连接都是新new出来的。

此时返回的status是这样的:

代码语言:javascript复制
DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);

其中transaction中的holder为新holder,连接都是新的,标记为新事务,在开头的回顾中提到了,如果是新事务,提交时才能成功提交。并且在最后一个参数放入挂起的对象,之后恢复它。

REQUIRES_NEW小结

会与前一个事务隔离,自己新开一个事务,与上一个事务无关,如果报错,上一个事务catch住异常,上一个事务是不会回滚的,这里要注意***(在invokeWithinTransaction方法中的catch代码块中,处理完异常后,还通过 throw ex;将异常抛给了上层,所以上层要catch住子事务的异常,子事务回滚后,上层事务也会回滚)**,而只要自己提交了之后,就算上一个事务后面的逻辑报错,自己是不会回滚的,(因为被标记为新事务,所以在提交阶段已经提交了)。

4. NESTED

不挂起事务,并且返回的status的对象如下:

代码语言:javascript复制
DefaultTransactionStatus status =prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);

status.createAndHoldSavepoint();

不同于其他的就是此传播特性会创建savePoint,有什么用呢?前面说到,如果是旧事务的话回滚是不会执行的,但先看看它的status,虽然标记为旧事务,但它还有savePoint如果有savePoint,会回滚到保存点去,提交的时候会释放保存点,但是不提交!切记,这里就是NESTED与REQUIRES_NEW不同点之一了,NESTED之后再外层事务成功的时候才会进行提交,实际提交点只是去释放保存点,外层事务失败,NESTED也将回滚,但如果是REQUIRES_NEW的话,不管外层事务是否成功,它都会提交不回滚,这就是savePoint的作用。

由于不挂起事务,可以看出来,此时transaction中的holder用的还是旧的,连接也是上一个事务的连接,可以看出来,这个传播特性也会将原事务和自己当成一个事务来做。

NESTED小结

与前一个事务不隔离,没有新开事务,用的也是老的transaction,老的holder,同样也是老的connection,没有挂起的事务。关键点在这个传播特性在存在事务情况下会创建savePoint,但不存在事务情况下是不会创建savePoint的。在提交时不真正提交,只是释放了保存点而已,在回滚时会回滚到保存点位置,如果上层事务catch住异常的话,是不会影响上层事务的提交的,外层事务提交时,会统一提交,外层事务回滚的话,会全部回滚

5. REQUIRED、PROPAGATION_REQUIRED或PROPAGATION_MANDATORY

存在事务加入事务即可,标记为旧事务,空挂起。

status:

代码语言:javascript复制
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);

使用旧事务,标记为旧事务,挂起对象为空。

与前一个事务不隔离 ,没有新开事务,用的也是老transaction,老的connection,但此时被标记成旧事务,所以,在提交阶段不会真正提交的,在外层事务提交阶段,才会把事务提交。

如果此时这里出现了异常,内层事务执行回滚时,旧事务是不会去回滚的,而是进行回滚标记,我们看看文章开头处回滚的处理函数processRollback中的doSetRollbackOnly(status);,当前事务信息中表明是存在事务的,但是既没有保存点,又不是新事务,回滚的时候只做回滚标识,等到提交的时候再判断是否有回滚标识,commit的时候,如果有回滚标识,就进行回滚

代码语言:javascript复制
@Override
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
    // 将status中的transaction取出
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    if (status.isDebug()) {
        logger.debug("Setting JDBC transaction ["   txObject.getConnectionHolder().getConnection()  
                            "] rollback-only");
    }
    // transaction执行标记回滚
    txObject.setRollbackOnly();
}
代码语言:javascript复制
public void setRollbackOnly() {
    // 这里将transaction里面的connHolder标记回滚
    getConnectionHolder().setRollbackOnly();
}
代码语言:javascript复制
public void setRollbackOnly() {
    // 将holder中的这个属性设置成true
    this.rollbackOnly = true;
}

我们知道再内层事务中transaction对象中的holder对象其实就是外层事务transaction里的holder,holder是一个对象,指向同一个地址,在这里设计holder标记,外层事务图transaction中的holder也是会被设置到的,在外层事务提交的时候有这样一段代码:

代码语言:javascript复制
@Override
public final void commit(TransactionStatus status) throws TransactionException {
    if (status.isCompleted()) {
        throw new IllegalTransactionStateException(
                            "Transaction is already completed - do not call commit or rollback more than once per transaction");
    }
    DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
    // 如果在事务链中已经被标记回滚,那么不会尝试提交事务,直接回滚
    if (defStatus.isLocalRollbackOnly()) {
        if (defStatus.isDebug()) {
            logger.debug("Transactional code has requested rollback");
        }
        processRollback(defStatus, false);
        return;
    }
    // !shouldCommitOnGlobalRollbackOnly()只有JTA与JPA事务管理器才会返回false
    // defStatus.isGlobalRollbackOnly()这里判断status是否被标记了
    if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
        if (defStatus.isDebug()) {
            logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
        }
        // 如果内层事务抛异常,外层事务是会走到这个方法中的,而不是去提交
        // 这里会进行回滚,并且抛出一个异常
        processRollback(defStatus, true);
        return;
    }
    // 如果没有被标记回滚之类的,这里才真正判断是否提交
    processCommit(defStatus);
}

在外层事务提交的时候是会去验证transaction中的holder里是否被标记了rollback了,外层事务回滚,将会标记holder,而holder是线程变量,在此传播特性中holder是同一个对象,外层事务将无法正常提交而进入processRollback方法进行回滚,并抛出异常:

代码语言:javascript复制
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
    try {
        // 此时这个值为true
        boolean unexpectedRollback = unexpected;

        try {
            triggerBeforeCompletion(status);

            if (status.hasSavepoint()) {
                if (status.isDebug()) {
                    logger.debug("Rolling back transaction to savepoint");
                }
                status.rollbackToHeldSavepoint();
            }
            // 新事务,将进行回滚操作
            else if (status.isNewTransaction()) {
                if (status.isDebug()) {
                    logger.debug("Initiating transaction rollback");
                }
                // 回滚!
                doRollback(status);
            }
            
         // 略...
            
        // Raise UnexpectedRollbackException if we had a global rollback-only marker
        // 抛出一个异常
        if (unexpectedRollback) {
            // 这个就是上文说到的抛出的异常类型
            throw new UnexpectedRollbackException(
                    "Transaction rolled back because it has been marked as rollback-only");
        }
    }
    finally {
        cleanupAfterCompletion(status);
    }
}

至此,关于Spring的7种传播级别就讲的差不多了。

0 人点赞