Spring 事务

Spring 事务是在数据库事务的基础上完成的,Spring 事务框架就是这样的一种管理事务的框架,它的作用和我们手动地使用mysql命令处理事务没有什么不同,只是做了一些更好的封装。可以思考一下如果我们想要写一个事务管理框架并交给 Spring 容器来管理,那么我们将面临哪些问题?我们得解决哪些问题?显然这个框架的问题域是 mysql 事务,那么 mysql 事务有哪些操作,用法,我们的框架就将有哪些用法,它能向下管理底层的 mysql 事务,那么它就会拥有向上提供mysql事务的能力,实际上这个框架就是mysql事务语句的java版的能力映射。比如,mybatis是mysql的中间件,一段mybatis代码可以映射为一条mysql 增删改查的语句,它们是一一对应的,就像翻译一样,对于其它 java 程序,mybatis就代表了一种数据库能力,所以,所有可能的mysql语句,都得被mybatis所能表达的能力覆盖,这就是问题域,它得解决哪些问题。

有两种事务使用方式,一种是设置本会话的autocommit=0,然后使用commit手动提交,另一种是使用begin 或start transaction开启事务,使用commit进行提交;代码中使用前者。

if (con.getAutoCommit()) {
    txObject.setMustRestoreAutoCommit(true);
    if (logger.isDebugEnabled()) {
        logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
    }
    con.setAutoCommit(false);
}

在嵌套事务的内部,可以支持savepoint的机制设置回滚点,回滚到某个保存点后就不再继续回滚,这就是Spring事务所要支持的;只需要记得,两个@Transactional修饰的方法的嵌套调用,它们其实是两个代理逻辑就行,只不过需要注意要及时维护线程绑定的事务管理环境。

事务失效场景

我们可以从Spring 事务失效的场景来分析spring 事务要注意的方面。

  1. @Transactional修饰的方法是private的而不是public的,或类和方法是被static,final修饰的,那么事务失效,因为Spring 事务是通过Spring Aop来完成的,Spring Aop底层使用了 Cglib 动态代理,而Cglib 是通过子类继承和覆写来进行功能增强,final类不能被继承,final方法无法覆写,static修饰的方法是类的方法,不属于任何对象,代理是通过代理的方式代理某个对象,所以static方法不能被重写,即便写法上是重写,但是并不具备重写的含义,也就是说static方法也不被进行动态代理。

  1. 当事务发生异常时,该异常不是Spring 事务支持的异常,事务失效。可以在rollbackFor进行配置。或者catch了异常,但没有抛出异常(比如throw new RuntimeException()),也会导致事务失效,未抛出异常,事务不会回滚。

  1. @Transactional所在类没有被Spring 容器所管理。解决方法,加@Service注解。

  1. this的问题,当它自己调用自己时,要使用代理类去调用。可以通过AopContext.currentProxy()获取本对象的代理类。

  1. 多线程导致事务失效,不同线程获取到的数据库连接不一样,SqlSession会话也不一样,属于不同的事务。只有同一个数据库连接,才能进行提交和回滚。

  1. 数据源没有配置事务管理器,导致事务失效。

  1. 传播类型不支持事务,导致事务失效。这个需要注意。

这些事务失效场景在Spring 事务框架代码是怎么体现的?比如嵌套事务是怎么实现的,回滚和保存点在哪里?这些都需要去查看代码。

事务执行过程

因为Spring 事务是通过Spring Aop来实现的,那么它就会进入到拦截器链去执行。在ReflectiveMethodInvocation#proceed() 打断点。

Spring 事务_第1张图片

发现拦截器链里只有一个拦截器,叫TransactionInterceptor,执行完这个拦截器后就会进入invokeJoinpoint() 方法去执行原始的方法。TransactionInterceptor里会完成Spring 事务的封装操作。Spring 事务对事务的封装操作主要依赖于事务管理器,比如我们mybatis就会使用DataSourceTransactionManager,DataSourceTransactionManager的大部分操作都在它的父类AbstractPlatformTransactionManager中完成。

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
        return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
            @Override
            @Nullable
            public Object proceedWithInvocation() throws Throwable {
                return invocation.proceed();
            }
            @Override
            public Object getTarget() {
                return invocation.getThis();
            }
            @Override
            public Object[] getArguments() {
                return invocation.getArguments();
            }
        });
    }
}

TransactionAspectSupport#invokeWithinTransaction()方法比较长,我截取它实际运行的逻辑代码。这个方法里调用了很多的子方法。我们可以通过查看这些子方法来了解它的内部实现过程。因为我们的demo 使用了@Transactional 注解,所以是声明式事务,如果是编程式事务会走另一套逻辑,所以这里走声明式事务的逻辑。

protected Object invokeWithinTransaction(Method method, @Nullable Class targetClass,
            final InvocationCallback invocation) throws Throwable {

        // If the transaction attribute is null, the method is non-transactional.
        TransactionAttributeSource tas = getTransactionAttributeSource();
        final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        final TransactionManager tm = determineTransactionManager(txAttr);
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

        if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
            // Standard transaction demarcation with getTransaction and commit/rollback calls.
            TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
            Object retVal;
            try {
                retVal = invocation.proceedWithInvocation();
            }
            catch (Throwable ex) {
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }
            finally {
                cleanupTransactionInfo(txInfo);
            }
            if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
                TransactionStatus status = txInfo.getTransactionStatus();
                if (status != null && txAttr != null) {
                    retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
                }
            }
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }
}

里面最重要的方法就是 createTransactionIfNecessary() ,

protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
            @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
        if (txAttr != null && txAttr.getName() == null) {
            txAttr = new DelegatingTransactionAttribute(txAttr) {
                @Override
                public String getName() {
                    return joinpointIdentification;
                }
            };
        }
        TransactionStatus status = null;
        if (txAttr != null) {
            if (tm != null) {
                status = tm.getTransaction(txAttr);
            }
            else {
                ...
            }
        }
        return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
    }

getTransaction()获取事务。

public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
        @Override
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
            throws TransactionException {

        TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

        Object transaction = doGetTransaction();
        boolean debugEnabled = logger.isDebugEnabled();

        if (isExistingTransaction(transaction)) {
            return handleExistingTransaction(def, transaction, debugEnabled);
        }
        if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
            throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
        }

        // 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'");
        }
        else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
                def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            SuspendedResourcesHolder suspendedResources = suspend(null);
            
            try {
                return startTransaction(def, transaction, debugEnabled, suspendedResources);
            }
            catch (RuntimeException | Error ex) {
                resume(null, suspendedResources);
                throw ex;
            }
        }
        else { 
            ...
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
        }
    }
}

如果在准备创建事务时发现已存在事务,那么就会根据事务传播规则创建新事务或抛出异常。旧事务会被suspend()悬挂起来,等到恢复执行时会被resume(),它的信息我们使用SuspendedResourcesHolder 保存起来,所以新事务里会保存上一个事务的所有信息,所以事务可以无限嵌套下去。

    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) {
            if (debugEnabled) {
                logger.debug("Suspending current transaction");
            }
            Object suspendedResources = suspend(transaction);
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            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() + "]");
            }
            SuspendedResourcesHolder suspendedResources = suspend(transaction);
            try {
                return startTransaction(definition, transaction, debugEnabled, suspendedResources);
            }
            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.
                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");
                }
            }
        }
        boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
        return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
    }

注意,只有一种情况下会创建保存点,那就是事务传播行为为PROPAGATION_NESTED时。这个可以通过查看代码得知。

最后准备好了一个新事务的所有信息放入TransactionInfo对象中,并把该对象放入ThreadLocal绑定到线程里。

然后开始执行@Transactional修饰的方法。

然后处理回滚信息,这个和保存点有关,遇到保存点就不再继续回滚了。然后执行提交。事务执行完毕。清理事务信息。

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;
        }

        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);
    }
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
        try {
            boolean beforeCompletionInvoked = false;

            try {
                boolean unexpectedRollback = false;
                prepareForCommit(status);
                triggerBeforeCommit(status);
                triggerBeforeCompletion(status);
                beforeCompletionInvoked = true;

                if (status.hasSavepoint()) {
                    if (status.isDebug()) {
                        logger.debug("Releasing transaction savepoint");
                    }
                    unexpectedRollback = status.isGlobalRollbackOnly();
                    status.releaseHeldSavepoint();
                }
                else if (status.isNewTransaction()) {
                    if (status.isDebug()) {
                        logger.debug("Initiating transaction commit");
                    }
                    unexpectedRollback = status.isGlobalRollbackOnly();
                    doCommit(status);
                }
                else if (isFailEarlyOnGlobalRollbackOnly()) {
                    unexpectedRollback = status.isGlobalRollbackOnly();
                }

                // Throw UnexpectedRollbackException if we have a global rollback-only
                // marker but still didn't get a corresponding exception from commit.
                if (unexpectedRollback) {
                    throw new UnexpectedRollbackException(
                            "Transaction silently rolled back because it has been marked as rollback-only");
                }
            }
            catch (UnexpectedRollbackException ex) {
                // can only be caused by doCommit
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
                throw ex;
            }
            catch (TransactionException ex) {
                // can only be caused by doCommit
                if (isRollbackOnCommitFailure()) {
                    doRollbackOnCommitException(status, ex);
                }
                else {
                    triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
                }
                throw ex;
            }
            catch (RuntimeException | Error ex) {
                if (!beforeCompletionInvoked) {
                    triggerBeforeCompletion(status);
                }
                doRollbackOnCommitException(status, ex);
                throw ex;
            }

            // Trigger afterCommit callbacks, with an exception thrown there
            // propagated to callers but the transaction still considered as committed.
            try {
                triggerAfterCommit(status);
            }
            finally {
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
            }

        }
        finally {
            cleanupAfterCompletion(status);
        }
    }

在 cleanupAfterCompletion(status) 方法中,如果本事务之前存在事务,它会通过resume()方法唤醒老事务继续执行。

private void cleanupAfterCompletion(DefaultTransactionStatus status) {
        status.setCompleted();
        if (status.isNewSynchronization()) {
            TransactionSynchronizationManager.clear();
        }
        if (status.isNewTransaction()) {
            doCleanupAfterCompletion(status.getTransaction());
        }
        if (status.getSuspendedResources() != null) {
            if (status.isDebug()) {
                logger.debug("Resuming suspended transaction after completion of inner transaction");
            }
            Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
            resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
        }
    }

在resume()中把原来的旧的事务信息再绑定到本线程中去。TransactionSynchronization是事务扩展的钩子。

ps:在使用Spring 嵌套事务时,如果两个方法都操作同一张表,很可能会导致发生死锁,我刚好就遇到过。

未完待续。。。

你可能感兴趣的:(spring,学习,java)