美文网首页
Spring中事务传播级别的理解

Spring中事务传播级别的理解

作者: 狒狒_94d7 | 来源:发表于2020-04-09 16:29 被阅读0次

    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<TransactionSynchronization> 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。

    相关文章

      网友评论

          本文标题:Spring中事务传播级别的理解

          本文链接:https://www.haomeiwen.com/subject/ihoqmhtx.html