Spring事务处理过程

965 阅读10分钟

参考资料:

1.Spring事务原理深入解析(强烈推荐)

总结

Spring把数据源对应的数据库连接存放在ThreadLocal的Map中,key是数据源,value是数据库连接。这样做的好处是保证同一个线程中操作的数据库连接都是相同的,而且业务层使用事务时不需要感知和管理数据库连接。

经过下面分析,可以总结出Spring事务处理的过程:

  1. 先获取@Transactional配置的属性值,包括发生什么异常进行回滚、事务传播行为、事务的隔离级别、事务管理器等
  2. 获取事务管理器,如果@Transactional中没有配置,则从spring容器中获取
  3. 收集事务信息,在这一步,根据事务传播行为进行不同的处理,如果是requires_new,则挂起父事务,开启子事务,如果是required,则加入当前事务,如果当前事务没开启,则开启新事务等
  4. 执行目标方法
  5. 执行过程中,发生异常,判断该异常是否为@Transactional的rollbackFor配置的异常相同或者其子类,是的话回滚,否则提交事务。
  6. 清理事务环境,清除ThreadLocal相关变量,就好像没有开启过事务一样
  7. 没有发生异常,最后提交或者回滚事务。如果当前事务设置了回滚标志位,则回滚事务(回滚到保存点、整个事务回滚)。如果当前事务是新事务,则提交事务

事务的传播行为

required(常用)如果当前线程已经在一个事务中,则加入事务;如果没有事务,则新建一个
requires_new(常用)不管是否存在事务,都新建一个事务,挂起当前事务
support如果当前线程有事务,则加入。否则不使用事务
not_supported如果当前线程有事务,则挂起事务;不支持使用事务
mandatory如果当前线程有事务,则加入。否则抛出异常
never不支持事务,当前线程事务则抛出异常
nested嵌套事务,类似required,但有区别。mysql采用保存点实现
image-20210307213641124

image-20210307213653436

主要流程

Spring事务的处理流程代码主要在TransactionAspectSupport#invokeWithinTransaction

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();
    
		// 1.获取@Transactional的属性值
		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
		// 2.获取事务管理器
		final PlatformTransactionManager tm = determineTransactionManager(txAttr);
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

		if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
			// Standard transaction demarcation with getTransaction and commit/rollback calls.
			// 3.收集事务信息,开启或加入事务,具体操作根据配置的事务传播特性
			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

			Object retVal;
			try {
				// This is an around advice: Invoke the next interceptor in the chain.
				// This will normally result in a target object being invoked.
				// 4.执行被拦截的方法
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// target invocation exception
				// 5.根据@Transactional配置的rollbackFor指定的异常,决定回滚还是提交事务。
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				// 6.清除ThreadLocal设置的变量,好像没开启过事务一样
				cleanupTransactionInfo(txInfo);
			}
			// 7.提交或回滚事务
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

		else {
            // 编程式事务的处理过程(略)
        }

获取事务属性

@Transactional有以下属性:

image-20210308204615000

主要关注的字段:

  1. rollbackFor:发生xx异常执行回滚,默认是RuntimeException和Error
  2. propagation:传播行为(默认是required、requiresNew、support等)
  3. isolation:隔离级别(读未提交、读已提交、可重复读、串行化)

获取事务管理器

事务管理器:TransactionManager,保存着当前数据源连接,提供数据库事务的相关操作方法,包括开启、挂起和回滚事务。一个数据源需要一个事务管理器。

image-20210308210015917

事务管理器的获取过程:

  1. 先从@Transactional的属性配置中获取;
  2. 如果注解没有指定事务管理器,则从spring容器找到一个实现PlatformTransactionManager接口的实例对象。如果有多个实现该接口的实例对象,同时没有使用@Primary标注的话,就会报错

收集事务信息(重要)

代码入口是:TransactionAspectSupport#createTransactionIfNecessary

protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
			@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
		// 省略部分代码

		TransactionStatus status = null;
		if (txAttr != null) {
			if (tm != null) {
				// 获取事务状态
				status = tm.getTransaction(txAttr);
			}
			// 省略部分代码
		}
		// 准备一个事务信息(TransactionInfo)
		return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
	}

主要分析tm.getTransaction(txAttr)方法,处理过程如下:

  1. 获取事务对象(DataSourceTransactionObject)
  2. 判断当前线程是否已经开启事务,如果是的话,则根据传播行为进行相应处理
  3. 如果没有开启事务,则构建事务状态,开始新事务

详细代码如下:

@Override
	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.
			// 3.存在事务,则按照传播行为执行不同操作
            return handleExistingTransaction(definition, transaction, debugEnabled);
		}

		// ... 省略部分代码
		
        // 4.当前线程没有开启事务,即第一次开启事务,执行下面的代码,根据传播行为处理
		// 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) {
			SuspendedResourcesHolder suspendedResources = suspend(null);
			if (debugEnabled) {
				logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
			}
			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 ex) {
				resume(null, suspendedResources);
				throw ex;
			}
		}
		// ... 省略部分代码
	}

获取事务对象

org.springframework.jdbc.datasource.DataSourceTransactionManager#doGetTransaction

@Override
protected Object doGetTransaction() {
    DataSourceTransactionObject txObject = new DataSourceTransactionObject();
    txObject.setSavepointAllowed(isNestedTransactionAllowed());
    // 从TransactionSynchronizationManager中根据当前数据源获取到一个持有数据库连接的对象 ConnectionHolder
    ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
    txObject.setConnectionHolder(conHolder, false);
    return txObject;
}

TransactionSynchronizationManager是每个线程的事务同步管理器,用来管理事务用到的相关资源。在这个类中,我们可以看到很多ThreadLocal变量

// 记录了当前线程中的一些事务资源,已知的有根据DataSource作为key,数据库链接作为value,在执行doBegin操作时会写入新的键值对

private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");

private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
    new NamedThreadLocal<>("Transaction synchronizations");

private static final ThreadLocal<String> currentTransactionName =
    new NamedThreadLocal<>("Current transaction name");

private static final ThreadLocal<Boolean> currentTransactionReadOnly =
    new NamedThreadLocal<>("Current transaction read-only status");

private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
    new NamedThreadLocal<>("Current transaction isolation level");

private static final ThreadLocal<Boolean> actualTransactionActive =
    new NamedThreadLocal<>("Actual transaction active");

判断当前线程是否开启事务

判断规则有两个:

  1. 当前线程的事务对象是否持有ConnectionHolder
  2. ConnectionHolder的事务是否处于激活状态

详细代码如下:DataSourceTransactionManager#isExistingTransaction

@Override
protected boolean isExistingTransaction(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}

如果当前线程不存在事务,开始新事务

开启事务需要执行以下几步:

  1. 构建事务状态(TransactionStatus)
  2. 执行doBegin操作
  3. 将当前事务的相关属性设置到ThreadLocal,比如隔离级别、传播行为

先来介绍TransactionStatus,当事务管理器对事务进行提交或回滚操作时,需要用到该对象。主要记录的内容包括:

  • 当前事务是否为一个新事务,还是加入到别人的事务。(required 和 requires_new需要用到)
  • 是否有保存点
  • 是否已经设置回滚标志位。(设置回滚标志位,并不会真正执行回滚,而是根据传播行为决定何时回滚以及是否真正回滚)
  • 事务是否已经完成

image-20210308214025177

在获取到事务状态后,执行doBegin操作,主要做4件事情:

  1. 根据数据源对象获取一个数据库连接
  2. 设置隔离级别
  3. 执行con.setAutoCommit(false),开启事务
  4. 将这个数据库连接con绑定到当前线程。DataSource作为key,con作为value,将这个键值对保存到ThreadLocal的一个Map

最后,把事务状态、事务管理器、事务属性封装成一个TransactionInfo对象。

如果当前线程已经存在事务

主要讨论常用的requires_new和required的区别

/**
  * Create a TransactionStatus for an existing transaction.
  */
private TransactionStatus handleExistingTransaction(
    TransactionDefinition definition, Object transaction, boolean debugEnabled)
    throws TransactionException {

    // ... 省略其他传播行为

    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
        // 挂起事务
        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;
        }
    }

    // 执行到这里,说明传播行为是support 或者 required
    // Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
    // ... 省略部分代码
    return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}

从上面的代码可以看到,当隔离级别为requires_new时,先挂起一个事务然后,然后开启一个新事务。当隔离级别为support或者required时,直接返回,没有挂起或者doBegin操作。

注意:当传播行为是required时,隔离级别不会改变,仍然是上个事务的隔离级别。

异常处理

当收集完事务信息后,就执行目标方法,如果执行过程中发生异常,根据@Transactional配置的rollbackFor指定的异常,决定回滚还是提交事务。

/**
  * Handle a throwable, completing the transaction.
  * We may commit or roll back, depending on the configuration.
  * @param txInfo information about the current transaction
  * @param ex throwable encountered
  */
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
        // ...省略部分代码
        
        // 如果抛出的异常是指定的回滚异常,或者默认值,则执行回滚。否则,提交事务
        if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
            try {
                txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
            }
            // ...省略部分代码
        }
        else {
            // We don't roll back on this exception.
            // Will still roll back if TransactionStatus.isRollbackOnly() is true.
            try {
                txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
            }
            // ...省略部分代码
        }
    }
}

提交事务

当没有异常发生,根据是否设置了回滚标志位,决定是回滚还是提交事务。

/**
  * Execute after successful completion of call, but not after an exception was handled.
  * Do nothing if we didn't create a transaction.
  * @param txInfo information about the current transaction
  */
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
        if (logger.isTraceEnabled()) {
            logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
        }
        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
    }
}

/**
  * This implementation of commit handles participating in existing
  * transactions and programmatic rollback requests.
  * Delegates to {@code isRollbackOnly}, {@code doCommit}
  * and {@code rollback}.
  * @see org.springframework.transaction.TransactionStatus#isRollbackOnly()
  * @see #doCommit
  * @see #rollback
  */
@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;
    }
	// ... 省略部分代码
    // 提交事务
    processCommit(defStatus);
}

processCommit

在真正执行提交的地方,需要判断当前事务是否为新事务,是的话,才执行提交操作。

/**
  * Process an actual commit.
  * Rollback-only flags have already been checked and applied.
  * @param status object representing the transaction
  * @throws TransactionException in case of commit failure
  */
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");
            }
        }
        // ...省略部分代码
    }
    finally {
        cleanupAfterCompletion(status);
    }
}

processRollback

回滚的过程是,如果有保存点,则会回滚到保存点。如果是新事务,则直接执行回滚操作。如果加入到其他事务中,不执行回滚,而是设置回滚标志位。

/**
  * Process an actual rollback.
  * The completed flag has already been checked.
  * @param status object representing the transaction
  * @throws TransactionException in case of rollback failure
  */
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
    try {
        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);
            }
            else {
                // 加入到其他事务,设置回滚标志位
                // Participating in larger transaction
                if (status.hasTransaction()) {
                    if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                        if (status.isDebug()) {
                            logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
                        }
                        doSetRollbackOnly(status);
                    }
                  
                // ... 省略部分代码 
    finally {
        cleanupAfterCompletion(status);
    }
}