springboot事务管理实现原理解析

  事务在关系型数据库中是一非常重要的东西,spring中对事务的处理也很简单,也就是加个注解的事。为更进一步理解它是如何实现的,今天我们就来拆解下。

1. 几个核心问题

  1. 事务的核心是什么?

    简单说其实就是底层数据库的使用方法,比如通常的sql begin; commit; rollback;... 各数据库各自的都会有自己的一套描述,并不完全一样。但总是因为db层面支持事务,在spring中才会支持事务。即spring只是上层应用。

  2. 事务相关的代码是否必须写在一块?

    就数据库sql操作而言,一般是要求写在一块,一起执行。比如: 开启事务 -> 执行业务1 -> n... -> 提交事务;但实际上可以看出,事务只要声明开始和结束,就可以做一起提交的效果。但是,开启事务后,中间所有的操作,就不能不受事务控制了。除非另外开一个子事务(这就有点复杂了)。那么,spring又如何去实现这种功能呢?

  3. 如何将spring和具体的数据库事务关联起来?

    比如,数据库有很多种,CRM框架也有很多,如何通过spring的事务框架统一管理起来呢?

  4. 如何管理多种不同数据源的事务呢?

    更复杂的事务是,我有多个数据源,又怎么去管理呢?能否做到部分事务管理呢?而非统一管理。这里可能更多的是一种api的使用方法了,咱们可以在需要使用时去翻阅官方文档。

 

2. 来个事务例子

  spring中的事务操作很简单,比较常用的spring+mybatis方式,更是简单。只需要在需要实现事务的方法上添加注解即可。但该方法必须是独立的类的第一个方法,不允许是内部方法调用。这个问题的原因是事务需要用到切面完成,而方法内部调用无法走出类被代理而是直达方法。

@RestController
@RequestMapping("/hello")
@Slf4j
public class HelloController {

    @Resource
    private UserService userService;

    // 请求入口
    @GetMapping("/transInsert")
    @ResponseBody
    public Object transInsert(@RequestParam Long actorId,
                              @RequestParam String firstName,
                              @RequestParam String lastName,
                              @RequestParam Long filmId) {
        // 事务入口
        Object data = userService.insertActorAndFilmActor(
                        actorId, firstName, lastName, filmId);
        return data;
    }
}


@Service
@Slf4j
public class UserService {

    @Resource
    private UserMapper userMapper;

    // 事务开启
    @Transactional(transactionManager = "transactionManager")
    public Object insertActorAndFilmActor(Long actorId,
                                          String firstName,
                                          String lastName,
                                          Long filmId) {
        ActorEntity actorEntity = new ActorEntity();
        actorEntity.setActorId(actorId);
        actorEntity.setFirstName(firstName);
        actorEntity.setLastName(lastName);
        // 业务操作1
        Integer affect = userMapper.insertActor(actorEntity);

        actorId = actorEntity.getActorId();
        FilmActorEntity filmActorEntity = new FilmActorEntity();
        filmActorEntity.setActorId(actorId);
        filmActorEntity.setFilmId(filmId);
        // 业务操作2
        affect += userMapper.insertFilmActor(filmActorEntity);
        // 退出后业务提交
        return affect;
    }

}

  效果就是,要么两条数据都插入成功,要么都不成功。其中需要注意的是,springboot中需要开启事务,即在配置类中需要添加如下注解。

@EnableTransactionManagement
...

 

3. spring事务的实现过程

  @Transactional 标识开启事务,当方法调用到这里之后,会先到 TransanctionInterceptor.invoke(); 然后就接入到了事务的管理过程了。我们可以从这里入手。

    // org.springframework.transaction.interceptor.TransactionInterceptor#invoke
    @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);
    }

    // org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
    /**
     * General delegate for around-advice-based subclasses, delegating to several other template
     * methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager}
     * as well as regular {@link PlatformTransactionManager} implementations and
     * {@link ReactiveTransactionManager} implementations for reactive return types.
     * @param method the Method being invoked
     * @param targetClass the target class that we're invoking the method on
     * @param invocation the callback to use for proceeding with the target invocation
     * @return the return value of the method, if any
     * @throws Throwable propagated from the target invocation
     */
    @Nullable
    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);

        if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
            ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
                if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
                    throw new TransactionUsageException(
                            "Unsupported annotated transaction on suspending function detected: " + method +
                            ". Use TransactionalOperator.transactional extensions instead.");
                }
                ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
                if (adapter == null) {
                    throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +
                            method.getReturnType());
                }
                return new ReactiveTransactionSupport(adapter);
            });
            return txSupport.invokeWithinTransaction(
                    method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);
        }

        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 {
                // This is an around advice: Invoke the next interceptor in the chain.
                // This will normally result in a target object being invoked.
                // 调用业务方法
                retVal = invocation.proceedWithInvocation();
            }
            catch (Throwable ex) {
                // target invocation exception
                // 抛出异常,事务回滚
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }
            finally {
                cleanupTransactionInfo(txInfo);
            }

            if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
                // Set rollback-only in case of Vavr failure matching our rollback rules...
                TransactionStatus status = txInfo.getTransactionStatus();
                if (status != null && txAttr != null) {
                    retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
                }
            }
            // 提交事务
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }

        else {
            Object result;
            final ThrowableHolder throwableHolder = new ThrowableHolder();

            // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
            try {
                result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> {
                    TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
                    try {
                        Object retVal = invocation.proceedWithInvocation();
                        if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
                            // Set rollback-only in case of Vavr failure matching our rollback rules...
                            retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
                        }
                        return retVal;
                    }
                    catch (Throwable ex) {
                        if (txAttr.rollbackOn(ex)) {
                            // A RuntimeException: will lead to a rollback.
                            if (ex instanceof RuntimeException) {
                                throw (RuntimeException) ex;
                            }
                            else {
                                throw new ThrowableHolderException(ex);
                            }
                        }
                        else {
                            // A normal return value: will lead to a commit.
                            throwableHolder.throwable = ex;
                            return null;
                        }
                    }
                    finally {
                        cleanupTransactionInfo(txInfo);
                    }
                });
            }
            catch (ThrowableHolderException ex) {
                throw ex.getCause();
            }
            catch (TransactionSystemException ex2) {
                if (throwableHolder.throwable != null) {
                    logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
                    ex2.initApplicationException(throwableHolder.throwable);
                }
                throw ex2;
            }
            catch (Throwable ex2) {
                if (throwableHolder.throwable != null) {
                    logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
                }
                throw ex2;
            }

            // Check result state: It might indicate a Throwable to rethrow.
            if (throwableHolder.throwable != null) {
                throw throwableHolder.throwable;
            }
            return result;
        }
    }

  以上就是整个事务的管理框架,先获取事务配置信息,然后开启事务,执行业务代码,最后进行提交或者回滚事务。清理现场。逻辑非常清晰易懂。

  下面让我们更深入理解下,创建事务,提交事务,回滚事务的过程吧。

事务创建与绑定:

    // 事务调入
    // org.springframework.transaction.interceptor.TransactionAspectSupport#createTransactionIfNecessary
    /**
     * Create a transaction if necessary based on the given TransactionAttribute.
     * <p>Allows callers to perform custom TransactionAttribute lookups through
     * the TransactionAttributeSource.
     * @param txAttr the TransactionAttribute (may be {@code null})
     * @param joinpointIdentification the fully qualified method name
     * (used for monitoring and logging purposes)
     * @return a TransactionInfo object, whether or not a transaction was created.
     * The {@code hasTransaction()} method on TransactionInfo can be used to
     * tell if there was a transaction created.
     * @see #getTransactionAttributeSource()
     */
    @SuppressWarnings("serial")
    protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
            @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {

        // If no name specified, apply method identification as transaction name.
        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 {
                if (logger.isDebugEnabled()) {
                    logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
                            "] because no transaction manager has been configured");
                }
            }
        }
        return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
    }

    // org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction
    /**
     * This implementation handles propagation behavior. Delegates to
     * {@code doGetTransaction}, {@code isExistingTransaction}
     * and {@code doBegin}.
     * @see #doGetTransaction
     * @see #isExistingTransaction
     * @see #doBegin
     */
    @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);
        }

        // Check definition settings for new transaction.
        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);
            if (debugEnabled) {
                logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
            }
            try {
                // 事务开启
                return startTransaction(def, transaction, debugEnabled, suspendedResources);
            }
            catch (RuntimeException | Error ex) {
                resume(null, suspendedResources);
                throw ex;
            }
        }
        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);
            }
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
        }
    }

    // org.springframework.transaction.support.AbstractPlatformTransactionManager#startTransaction
    /**
     * Start a new transaction.
     */
    private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
            boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {

        boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
        DefaultTransactionStatus status = newTransactionStatus(
                definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
        // 具体的事务处理
        doBegin(transaction, definition);
        prepareSynchronization(status, definition);
        return status;
    }

    // org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
    @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);
            }

            txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
            // 多次进入复用db连接
            con = txObject.getConnectionHolder().getConnection();

            Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
            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");
                }
                // 事务开启即是设置 auto commit=0
                con.setAutoCommit(false);
            }

            prepareTransactionalConnection(con, definition);
            // 设置开启事务标识,后续看到此标识将走事务逻辑
            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.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);
        }
    }

    // 具体mysql的实现 autocommit=0
    // com.mysql.cj.jdbc.ConnectionImpl#setAutoCommit
    public void setAutoCommit(final boolean autoCommitFlag) throws SQLException {
        try {
            synchronized(this.getConnectionMutex()) {
                this.checkClosed();
                if (this.connectionLifecycleInterceptors != null) {
                    IterateBlock<ConnectionLifecycleInterceptor> iter = new IterateBlock<ConnectionLifecycleInterceptor>(this.connectionLifecycleInterceptors.iterator()) {
                        void forEach(ConnectionLifecycleInterceptor each) throws SQLException {
                            if (!each.setAutoCommit(autoCommitFlag)) {
                                this.stopIterating = true;
                            }

                        }
                    };
                    iter.doForAll();
                    if (!iter.fullIteration()) {
                        return;
                    }
                }

                if ((Boolean)this.autoReconnectForPools.getValue()) {
                    this.autoReconnect.setValue(true);
                }

                try {
                    boolean needsSetOnServer = true;
                    if ((Boolean)this.useLocalSessionState.getValue() && this.session.getServerSession().isAutoCommit() == autoCommitFlag) {
                        needsSetOnServer = false;
                    } else if (!(Boolean)this.autoReconnect.getValue()) {
                        needsSetOnServer = this.getSession().isSetNeededForAutoCommitMode(autoCommitFlag);
                    }

                    this.session.getServerSession().setAutoCommit(autoCommitFlag);
                    if (needsSetOnServer) {
                        // 真正的事务开始,即设置 autocommit=0
                        this.session.execSQL((Query)null, autoCommitFlag ? "SET autocommit=1" : "SET autocommit=0", -1, (NativePacketPayload)null, false, this.nullStatementResultSetFactory, (ColumnDefinition)null, false);
                    }
                } finally {
                    if ((Boolean)this.autoReconnectForPools.getValue()) {
                        this.autoReconnect.setValue(false);
                    }

                }

            }
        } catch (CJException var12) {
            throw SQLExceptionsMapping.translateException(var12, this.getExceptionInterceptor());
        }
    }

  可以看到,事务的开启有几个重要操作,一个是创建数据库连接,二是设置autocommit=0,三是将连接绑定到当前线程备用。基本上我们就可以推断出其实现原理了。接着往下看。

 

业务代码的执行:

  事务开启之后,就进入了业务代码执行,然后才会有commit或rollback。实际上,业务代码往往是使用CRM框架来进行db操作的,我们此处使用的是 Mybatis 。我们看看其如何和spring进行绑定的。

  实际上,mybatis会先获取一个会话session, 然后再获取db连接,最后执行sql语句。

    // org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 获取会话,建立数据源连接,事务内置
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

  // org.mybatis.spring.SqlSessionUtils#getSqlSession
  /**
   * Gets an SqlSession from Spring Transaction Manager or creates a new one if needed. Tries to get a SqlSession out of
   * current transaction. If there is not any, it creates a new one. Then, it synchronizes the SqlSession with the
   * transaction if Spring TX is active and <code>SpringManagedTransactionFactory</code> is configured as a transaction
   * manager.
   *
   * @param sessionFactory
   *          a MyBatis {@code SqlSessionFactory} to create new sessions
   * @param executorType
   *          The executor type of the SqlSession to create
   * @param exceptionTranslator
   *          Optional. Translates SqlSession.commit() exceptions to Spring exceptions.
   * @return an SqlSession managed by Spring Transaction Manager
   * @throws TransientDataAccessResourceException
   *           if a transaction is active and the {@code SqlSessionFactory} is not using a
   *           {@code SpringManagedTransactionFactory}
   * @see SpringManagedTransactionFactory
   */
  public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
    // 首次进入将新创建一个 sqlSessionHolder
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    LOGGER.debug(() -> "Creating a new SqlSession");
    session = sessionFactory.openSession(executorType);
    // 注册当前的 sessionHolder , 同时也会去拿取数据库连接,从而与spring 事务建立联系
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

  // org.mybatis.spring.SqlSessionUtils#registerSessionHolder
  /**
   * Register session holder if synchronization is active (i.e. a Spring TX is active).
   *
   * Note: The DataSource used by the Environment should be synchronized with the transaction either through
   * DataSourceTxMgr or another tx synchronization. Further assume that if an exception is thrown, whatever started the
   * transaction will handle closing / rolling back the Connection associated with the SqlSession.
   * 
   * @param sessionFactory
   *          sqlSessionFactory used for registration.
   * @param executorType
   *          executorType used for registration.
   * @param exceptionTranslator
   *          persistenceExceptionTranslator used for registration.
   * @param session
   *          sqlSession used for registration.
   */
  private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    // 前面设置了事务激活标识,此处即可识别
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
      Environment environment = sessionFactory.getConfiguration().getEnvironment();

      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
        LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");

        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        // 把当前sqlSessionFactory 绑定进去,避免后面反复查找性能损耗
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        TransactionSynchronizationManager
            .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        holder.setSynchronizedWithTransaction(true);
        holder.requested();
      } else {
        if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
          LOGGER.debug(() -> "SqlSession [" + session
              + "] was not registered for synchronization because DataSource is not transactional");
        } else {
          throw new TransientDataAccessResourceException(
              "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
        }
      }
    } else {
      LOGGER.debug(() -> "SqlSession [" + session
          + "] was not registered for synchronization because synchronization is not active");
    }

  }

  // 执行语句之前会获取db连接, 再真正与spring建立绑定
  // org.apache.ibatis.executor.SimpleExecutor#prepareStatement
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }
  // org.apache.ibatis.executor.BaseExecutor#getConnection
  protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }
  // org.mybatis.spring.transaction.SpringManagedTransaction#getConnection
  /**
   * {@inheritDoc}
   */
  @Override
  public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      // 首次进入会打开连接,事务时会直接承接之前创建的连接,从而使事务生效
      openConnection();
    }
    return this.connection;
  }
  // org.mybatis.spring.transaction.SpringManagedTransaction#openConnection
  /**
   * Gets a connection from Spring transaction manager and discovers if this {@code Transaction} should manage
   * connection or let it to Spring.
   * <p>
   * It also reads autocommit setting because when using Spring Transaction MyBatis thinks that autocommit is always
   * false and will always call commit/rollback so we need to no-op that calls.
   */
  private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

    LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will"
        + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
  }
    // org.springframework.jdbc.datasource.DataSourceUtils#getConnection
    /**
     * Obtain a Connection from the given DataSource. Translates SQLExceptions into
     * the Spring hierarchy of unchecked generic data access exceptions, simplifying
     * calling code and making any exception that is thrown more meaningful.
     * <p>Is aware of a corresponding Connection bound to the current thread, for example
     * when using {@link DataSourceTransactionManager}. Will bind a Connection to the
     * thread if transaction synchronization is active, e.g. when running within a
     * {@link org.springframework.transaction.jta.JtaTransactionManager JTA} transaction).
     * @param dataSource the DataSource to obtain Connections from
     * @return a JDBC Connection from the given DataSource
     * @throws org.springframework.jdbc.CannotGetJdbcConnectionException
     * if the attempt to get a Connection failed
     * @see #releaseConnection
     */
    public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
        try {
            return doGetConnection(dataSource);
        }
        catch (SQLException ex) {
            throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
        }
        catch (IllegalStateException ex) {
            throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage());
        }
    }
    /**
     * Actually obtain a JDBC Connection from the given DataSource.
     * Same as {@link #getConnection}, but throwing the original SQLException.
     * <p>Is aware of a corresponding Connection bound to the current thread, for example
     * when using {@link DataSourceTransactionManager}. Will bind a Connection to the thread
     * if transaction synchronization is active (e.g. if in a JTA transaction).
     * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}.
     * @param dataSource the DataSource to obtain Connections from
     * @return a JDBC Connection from the given DataSource
     * @throws SQLException if thrown by JDBC methods
     * @see #doReleaseConnection
     */
    public static Connection doGetConnection(DataSource dataSource) throws SQLException {
        Assert.notNull(dataSource, "No DataSource specified");
        // 一级缓存,使用dataSource绑定
        // 二级缓存,使用 SpringManagedTransaction 保存
        ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
            conHolder.requested();
            if (!conHolder.hasConnection()) {
                logger.debug("Fetching resumed JDBC Connection from DataSource");
                conHolder.setConnection(fetchConnection(dataSource));
            }
            return conHolder.getConnection();
        }
        // Else we either got no holder or an empty thread-bound holder here.

        logger.debug("Fetching JDBC Connection from DataSource");
        Connection con = fetchConnection(dataSource);

        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            try {
                // Use same Connection for further JDBC actions within the transaction.
                // Thread-bound object will get removed by synchronization at transaction completion.
                ConnectionHolder holderToUse = conHolder;
                if (holderToUse == null) {
                    holderToUse = new ConnectionHolder(con);
                }
                else {
                    holderToUse.setConnection(con);
                }
                holderToUse.requested();
                TransactionSynchronizationManager.registerSynchronization(
                        new ConnectionSynchronization(holderToUse, dataSource));
                holderToUse.setSynchronizedWithTransaction(true);
                if (holderToUse != conHolder) {
                    TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
                }
            }
            catch (RuntimeException ex) {
                // Unexpected exception from external delegation call -> close Connection and rethrow.
                releaseConnection(con, dataSource);
                throw ex;
            }
        }

        return con;
    }

  很显然,Mybatis和spring是两个独立的东西,但是它们通过DataSource绑定在了一块,通过 DataSourceUtils 将数据库连接复用起来。事务开启时放入的连接,在mybatis使用时,将其取出进行复用。从而保证了操作是在一连接下进行,尽管代码层面看起来是完全分开的。但实际上和我们写一连串的sql是一样的效果。

 

事务提交:

  业务代码执行成功后,就可以进行事务提交了,此时由spring统一进行处理。即获取到db连接,然后commit操作。

    // org.springframework.transaction.interceptor.TransactionAspectSupport#commitTransactionAfterReturning
    /**
     * 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());
        }
    }
    // org.springframework.transaction.support.AbstractPlatformTransactionManager#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");
                }
            }
            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);
        }
    }
    // org.springframework.jdbc.datasource.DataSourceTransactionManager#doCommit
    @Override
    protected void doCommit(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        if (status.isDebug()) {
            logger.debug("Committing JDBC transaction on Connection [" + con + "]");
        }
        try {
            con.commit();
        }
        catch (SQLException ex) {
            throw new TransactionSystemException("Could not commit JDBC transaction", ex);
        }
    }
    // com.mysql.cj.jdbc.ConnectionImpl#commit
    public void commit() throws SQLException {
        try {
            synchronized(this.getConnectionMutex()) {
                this.checkClosed();

                try {
                    if (this.connectionLifecycleInterceptors != null) {
                        IterateBlock<ConnectionLifecycleInterceptor> iter = new IterateBlock<ConnectionLifecycleInterceptor>(this.connectionLifecycleInterceptors.iterator()) {
                            void forEach(ConnectionLifecycleInterceptor each) throws SQLException {
                                if (!each.commit()) {
                                    this.stopIterating = true;
                                }

                            }
                        };
                        iter.doForAll();
                        if (!iter.fullIteration()) {
                            return;
                        }
                    }

                    if (this.session.getServerSession().isAutoCommit()) {
                        throw SQLError.createSQLException(Messages.getString("Connection.3"), this.getExceptionInterceptor());
                    }

                    if ((Boolean)this.useLocalTransactionState.getValue() && !this.session.getServerSession().inTransactionOnServer()) {
                        return;
                    }
                    // 执行 commit 语句
                    this.session.execSQL((Query)null, "commit", -1, (NativePacketPayload)null, false, this.nullStatementResultSetFactory, (ColumnDefinition)null, false);
                } catch (SQLException var10) {
                    if ("08S01".equals(var10.getSQLState())) {
                        throw SQLError.createSQLException(Messages.getString("Connection.4"), "08007", this.getExceptionInterceptor());
                    }

                    throw var10;
                } finally {
                    this.session.setNeedsPing((Boolean)this.reconnectAtTxEnd.getValue());
                }

            }
        } catch (CJException var13) {
            throw SQLExceptionsMapping.translateException(var13, this.getExceptionInterceptor());
        }
    }

 

事务回滚:

  在发生异常后,需要将已经执行的代码进行回滚。否则就会形成数据不一致。

    // org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing
    /**
     * 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 (logger.isTraceEnabled()) {
                logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
                        "] after exception: " + ex);
            }
            if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
                try {
                    // 回滚操作
                    txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
                }
                catch (TransactionSystemException ex2) {
                    logger.error("Application exception overridden by rollback exception", ex);
                    ex2.initApplicationException(ex);
                    throw ex2;
                }
                catch (RuntimeException | Error ex2) {
                    logger.error("Application exception overridden by rollback exception", ex);
                    throw ex2;
                }
            }
            else {
                // We don't roll back on this exception.
                // Will still roll back if TransactionStatus.isRollbackOnly() is true.
                try {
                    txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
                }
                catch (TransactionSystemException ex2) {
                    logger.error("Application exception overridden by commit exception", ex);
                    ex2.initApplicationException(ex);
                    throw ex2;
                }
                catch (RuntimeException | Error ex2) {
                    logger.error("Application exception overridden by commit exception", ex);
                    throw ex2;
                }
            }
        }
    }

    // 核心回滚方法
    // org.springframework.transaction.support.AbstractPlatformTransactionManager#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);
                        }
                        else {
                            if (status.isDebug()) {
                                logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
                            }
                        }
                    }
                    else {
                        logger.debug("Should roll back transaction but cannot - no transaction available");
                    }
                    // Unexpected rollback only matters here if we're asked to fail early
                    if (!isFailEarlyOnGlobalRollbackOnly()) {
                        unexpectedRollback = false;
                    }
                }
            }
            catch (RuntimeException | Error ex) {
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
                throw ex;
            }

            triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);

            // 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);
        }
    }
    // org.springframework.jdbc.datasource.DataSourceTransactionManager#doRollback
    @Override
    protected void doRollback(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        if (status.isDebug()) {
            logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
        }
        try {
            con.rollback();
        }
        catch (SQLException ex) {
            throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
        }
    }

    // com.mysql.cj.jdbc.ConnectionImpl#rollback()
    public void rollback() throws SQLException {
        try {
            synchronized(this.getConnectionMutex()) {
                this.checkClosed();

                try {
                    if (this.connectionLifecycleInterceptors != null) {
                        IterateBlock<ConnectionLifecycleInterceptor> iter = new IterateBlock<ConnectionLifecycleInterceptor>(this.connectionLifecycleInterceptors.iterator()) {
                            void forEach(ConnectionLifecycleInterceptor each) throws SQLException {
                                if (!each.rollback()) {
                                    this.stopIterating = true;
                                }

                            }
                        };
                        iter.doForAll();
                        if (!iter.fullIteration()) {
                            return;
                        }
                    }

                    if (this.session.getServerSession().isAutoCommit()) {
                        throw SQLError.createSQLException(Messages.getString("Connection.20"), "08003", this.getExceptionInterceptor());
                    } else {
                        try {
                            // 使用 rollback 命令回滚数据
                            this.rollbackNoChecks();
                        } catch (SQLException var11) {
                            if (!(Boolean)this.ignoreNonTxTables.getInitialValue() || var11.getErrorCode() != 1196) {
                                throw var11;
                            }
                        }
                    }
                } catch (SQLException var12) {
                    if ("08S01".equals(var12.getSQLState())) {
                        throw SQLError.createSQLException(Messages.getString("Connection.21"), "08007", this.getExceptionInterceptor());
                    } else {
                        throw var12;
                    }
                } finally {
                    this.session.setNeedsPing((Boolean)this.reconnectAtTxEnd.getValue());
                }
            }
        } catch (CJException var15) {
            throw SQLExceptionsMapping.translateException(var15, this.getExceptionInterceptor());
        }
    }
    private void rollbackNoChecks() throws SQLException {
        try {
            synchronized(this.getConnectionMutex()) {
                if (!(Boolean)this.useLocalTransactionState.getValue() || this.session.getServerSession().inTransactionOnServer()) {
                    this.session.execSQL((Query)null, "rollback", -1, (NativePacketPayload)null, false, this.nullStatementResultSetFactory, (ColumnDefinition)null, false);
                }
            }
        } catch (CJException var5) {
            throw SQLExceptionsMapping.translateException(var5, this.getExceptionInterceptor());
        }
    }

  至此,整个事务已操作完成。我们来总结下,整个事务的核心在于,创建一个切面,就业务代码包裹起来,然后,创建连接缓存起来,给业务代码复用。执行完成业务代码后,取回连接进行提交或回滚。然后清理现场。整个事务过程中大量使用了 ThreadLocal 技术进行变量共享。

    // org.springframework.transaction.support.TransactionSynchronizationManager#clear
    /**
     * Clear the entire transaction synchronization state for the current thread:
     * registered synchronizations as well as the various transaction characteristics.
     * @see #clearSynchronization()
     * @see #setCurrentTransactionName
     * @see #setCurrentTransactionReadOnly
     * @see #setCurrentTransactionIsolationLevel
     * @see #setActualTransactionActive
     */
    public static void clear() {
        // 此处使用大量的 ThreadLocal 进行线程通信,多方法复用变量
        synchronizations.remove();
        currentTransactionName.remove();
        currentTransactionReadOnly.remove();
        currentTransactionIsolationLevel.remove();
        actualTransactionActive.remove();
    }

 

4. 事务的初始化时机

  实际上,事务只是一个注解,和其他很多的注解一样,会在bean初始的时候一起处理掉,比如此处使用 UserService, 在被化此bean的时候,就会将事务逻辑包裹注入其中。从而在调用时就会先调用事务,再调用业务代码。

    // org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#determineTransactionAttribute
    /**
     * Determine the transaction attribute for the given method or class.
     * <p>This implementation delegates to configured
     * {@link TransactionAnnotationParser TransactionAnnotationParsers}
     * for parsing known annotations into Spring's metadata attribute class.
     * Returns {@code null} if it's not transactional.
     * <p>Can be overridden to support custom annotations that carry transaction metadata.
     * @param element the annotated method or class
     * @return the configured transaction attribute, or {@code null} if none was found
     */
    @Nullable
    protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
        for (TransactionAnnotationParser parser : this.annotationParsers) {
            TransactionAttribute attr = parser.parseTransactionAnnotation(element);
            if (attr != null) {
                return attr;
            }
        }
        return null;
    }
    // org.springframework.transaction.annotation.SpringTransactionAnnotationParser#parseTransactionAnnotation
    @Override
    @Nullable
    public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
        AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
                element, Transactional.class, false, false);
        if (attributes != null) {
            return parseTransactionAnnotation(attributes);
        }
        else {
            return null;
        }
    }
    // 详细参数配置解析
    protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
        RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();

        Propagation propagation = attributes.getEnum("propagation");
        rbta.setPropagationBehavior(propagation.value());
        Isolation isolation = attributes.getEnum("isolation");
        rbta.setIsolationLevel(isolation.value());
        rbta.setTimeout(attributes.getNumber("timeout").intValue());
        rbta.setReadOnly(attributes.getBoolean("readOnly"));
        rbta.setQualifier(attributes.getString("value"));

        List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
        for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
            rollbackRules.add(new RollbackRuleAttribute(rbRule));
        }
        for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
            rollbackRules.add(new RollbackRuleAttribute(rbRule));
        }
        for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
            rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
        }
        for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
            rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
        }
        rbta.setRollbackRules(rollbackRules);

        return rbta;
    }

  初始化完成bean之后,各个地方引用的都是被代理或被切面处理过的实例。所以事务方法可以在任何其他类引用的地方生效。多事务管理器通过 transactionManager 进行指定区分。实际上,它们仅仅是一种逻辑,即可以保证不同的事务管理器之间,使用不同的连接,从而达到多数据管理的效果。

 

posted @ 2023-07-30 15:20  阿牛20  阅读(702)  评论(1编辑  收藏  举报