mybatis源碼分析(四) mybatis與spring事務管理分析


mybatis源碼分析(四) mybatis與spring事務管理分析

 

一丶從jdbc的角度理解什么是事務

  從mysql獲取一個連接之后, 默認是自動提交, 即執行完sql之后, 就會提交事務. 這種事務的范圍是一條sql語句.   

  將該連接設置非自動提交, 可以執行多條sql語句, 然后由程序決定是提交事務, 還是回滾事務. 這也是我們常說的事務.

Connection connection = dataSource.getConnection();

// connection.setTransactionIsolation(level.getLevel()); //設置事務隔離級別

// 設置是否自動提交, 如果不是自動提交, 則是"開啟"事務
connection.setAutoCommit(desiredAutoCommit);

// connection預編譯statement, 並執行sql
Statement stmt=connection.preparedStatement();
stmt.execute(sql);

// 提交事務, 或者回滾
connection.commit();
//connection.rollback();

 

  從jdbc使用事務的角度來看, 事務主要是圍繞connection展開的, 所以誰可獲得connection, 即可控制事務.

 

 

二丶mybatis是如何使用事務的

   mybatis將jdbc中的事務操作抽象封裝成Transaction,用於管理connection的生命周期--創建, 准備, 提交/回滾 和關閉.

/**
 * Wraps a database connection.
 * Handles the connection lifecycle that comprises: its creation, preparation, commit/rollback and close.
 *
 * @author Clinton Begin
 */
public interface Transaction {

  /**
   * Retrieve inner database connection.
   * @return DataBase connection
   * @throws SQLException
   */
  Connection getConnection() throws SQLException;

  /**
   * Commit inner database connection.
   * @throws SQLException
   */
  void commit() throws SQLException;

  /**
   * Rollback inner database connection.
   * @throws SQLException
   */
  void rollback() throws SQLException;

  /**
   * Close inner database connection.
   * @throws SQLException
   */
  void close() throws SQLException;

  /**
   * Get transaction timeout if set.
   * @throws SQLException
   */
  Integer getTimeout() throws SQLException;

}

 

   mybatis提供了兩種事務實現,一種是完全由jdbc實現的事務JdbcTransaction,包括實現提交和回滾.一種是供容器管理整個生命周期的事務ManagedTransaction,其中將忽略提交和回滾事務的請求, 將提交和回滾事務由容器實現, 但其實這種事務很少用.

  JdbcTransaction:

public class JdbcTransaction implements Transaction {

  private static final Log log = LogFactory.getLog(JdbcTransaction.class);

  protected Connection connection;
  protected DataSource dataSource;
  protected TransactionIsolationLevel level;
  protected boolean autoCommit;

  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
    dataSource = ds;
    level = desiredLevel;
    autoCommit = desiredAutoCommit;
  }

  public JdbcTransaction(Connection connection) {
    this.connection = connection;
  }

  @Override
  public Connection getConnection() throws SQLException {
    if (connection == null) {
      openConnection();
    }
    return connection;
  }

  @Override
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      connection.commit();
    }
  }

  @Override
  public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]");
      }
      connection.rollback();
    }
  }

  @Override
  public void close() throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      connection.close();
    }
  }

  protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
    try {
      if (connection.getAutoCommit() != desiredAutoCommit) {
        if (log.isDebugEnabled()) {
          log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(desiredAutoCommit);
      }
    } catch (SQLException e) {
      // Only a very poorly implemented driver would fail here,
      // and there's not much we can do about that.
      throw new TransactionException("Error configuring AutoCommit.  "
          + "Your driver may not support getAutoCommit() or setAutoCommit(). "
          + "Requested setting: " + desiredAutoCommit + ".  Cause: " + e, e);
    }
  }

  protected void resetAutoCommit() {
    try {
      if (!connection.getAutoCommit()) {
        // MyBatis does not call commit/rollback on a connection if just selects were performed.
        // Some databases start transactions with select statements
        // and they mandate a commit/rollback before closing the connection.
        // A workaround is setting the autocommit to true before closing the connection.
        // Sybase throws an exception here.
        if (log.isDebugEnabled()) {
          log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(true);
      }
    } catch (SQLException e) {
      if (log.isDebugEnabled()) {
        log.debug("Error resetting autocommit to true "
            + "before closing the connection.  Cause: " + e);
      }
    }
  }

  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommit);
  }

  @Override
  public Integer getTimeout() throws SQLException {
    return null;
  }

}
View Code

 

  

  mybatis事務執行流程:

  1. 由於mybatis將事務抽取成一個接口, 便於管理, 所以可以在配置中配置事務管理的實現

  2. 解析配置, 將事務管理對象, 保存到Configuration中

  3. SqlSessionFactory創建SqlSession時, 將會同時注入tx對象

  4. SqlSession執行sql語句時, 會委派給Executor執行, Executor處理主要的邏輯之外, 事務將會委派給事務對象處理, 如從事務對象中獲取連接, 使用事務對象提交事務.

  //BaseExecutor
  // 在執行器里獲取Connection , 最后是委派給Transaction獲取,
  // 事務管理, 即是Connection是否設置自動提交, 以及將事務的回滾調用交給事務管理器管理
  protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

 

  Transaction封裝了connection,然后在transaction內部封裝調用connection的操作,如提供不同的Transaction, 來管理connection的生命周期. 

  

 

三丶spring是如何使用事務的

   srpingboot和mybatis整合,測試事務

   

  1) 入口

  在配置了DataSourceProperties屬性之后, 會創建DataSource, 之后會創建DataSourceTransactionManager作為事務管理器

  

   org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration

@Configuration
@ConditionalOnClass({ JdbcTemplate.class, PlatformTransactionManager.class })
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceTransactionManagerAutoConfiguration {

    @Configuration
    @ConditionalOnSingleCandidate(DataSource.class)
    static class DataSourceTransactionManagerConfiguration {

        private final DataSource dataSource;

        private final TransactionManagerCustomizers transactionManagerCustomizers;

        DataSourceTransactionManagerConfiguration(DataSource dataSource,
                ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
            this.dataSource = dataSource;
            this.transactionManagerCustomizers = transactionManagerCustomizers.getIfAvailable();
        }

        @Bean
        @ConditionalOnMissingBean(PlatformTransactionManager.class)
        public DataSourceTransactionManager transactionManager(DataSourceProperties properties) {
        // 默認使用DataSourceTransactionManager, 從使用的角度來說,具有通用性 DataSourceTransactionManager transactionManager
= new DataSourceTransactionManager(this.dataSource); if (this.transactionManagerCustomizers != null) { this.transactionManagerCustomizers.customize(transactionManager); } return transactionManager; } } }

 

  DataSourceTransactionManager繼承於AbstractPlatformTransactionManager,而AbstractPlatformTransactionManager位於spring的tx子項目中  

 

  2) spring-tx子項目

  該項目最主要是用於實現事務管理.

  2.1) 最核心接口就是PlatformTransactionManager接口, 定義了事務管理器.

  a) #getTransaction(TransactionDefinition): TransactionStatus

  Return a currently active transaction or create a new one, according to the specified propagation behavior.

  b) #commit(TransactionStatus): void

  Commit the given transaction, with regard to its status. If the transaction has been marked rollback-only programmatically, perform a rollback.

  c) #rollback(TransactionStatus): void

  Perform a rollback of the given transaction.

     --更詳細的文檔則需要看源碼或者API文檔

  2.2) TransactionDefinition 定義了事務的傳播行為, 隔離界別, 事務超時時間等

 

 

 

  2.3)TransactionStatus 定義了事務的狀態, 以便於在提交事務或者回滾事務時決定如何后續行為.

 

 

   3)使用@Transactional注解聲明事務

    聲明式事務,s是基於AOP實現的.Spring會對使用@Transactinal注解聲明的方法進行動態代理, 生成使用org.springframework.transaction.interceptor.TransactionInterceptor增強對應方法的對象..

 

  3.1) 事務切面方法實現

  // org.springframework.transaction.interceptor.TransactionAspectSupport

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

 

/**
     * 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.
     * @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 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.
            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.
                retVal = invocation.proceedWithInvocation();
            }
            catch (Throwable ex) {
                // target invocation exception
 completeTransactionAfterThrowing(txInfo, ex); throw ex;
            }
            finally {
                cleanupTransactionInfo(txInfo);
            }
            commitTransactionAfterReturning(txInfo); return retVal;
        }

        else {
            final ThrowableHolder throwableHolder = new ThrowableHolder();

            // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
            try {
                Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
                    TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); try {
                        return invocation.proceedWithInvocation();
                    }
                    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);
                    }
                });

                // Check result state: It might indicate a Throwable to rethrow.
                if (throwableHolder.throwable != null) {
                    throw throwableHolder.throwable;
                }
                return result;
            }
            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;
            }
        }
    }

 

 

    /**
     * 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); // 這里使用了配置的PlatformTransactionManager獲取事務狀態
            }
            else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
                            "] because no transaction manager has been configured");
                }
            }
        }
        return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
    }

 

   3.2) 查看springboot自動配置DataSourceTransactionManager實現

 

 

  

    @Override
    protected Object doGetTransaction() {
        DataSourceTransactionObject txObject = new DataSourceTransactionObject();
        txObject.setSavepointAllowed(isNestedTransactionAllowed());
        ConnectionHolder conHolder =
                (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
        txObject.setConnectionHolder(conHolder, false);
        return txObject; // 剛開始獲取事務時, 由於沒有開啟事務, 所以為null
    }

 

    /**
     * This implementation sets the isolation level but ignores the timeout.
     */
    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        Connection con = null;

        try {
            if (!txObject.hasConnectionHolder() ||
                    txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
                 // 從數據源中獲取connection
                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);
            con = txObject.getConnectionHolder().getConnection();

            Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
            txObject.setPreviousIsolationLevel(previousIsolationLevel);

            // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
            // so we don't want to do it unnecessarily (for example if we've explicitly
            // configured the connection pool to set it already).
            if (con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                if (logger.isDebugEnabled()) {
                    logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
                }
                con.setAutoCommit(false);
            }

            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()) {
// 將Connection和datasource關聯, 交由事務同步管理器保存管理, 使用ThreadLocal隔離
// TranscationSynchronizationManager也是Spring和mybatis-spring共同合作管理事務的橋梁
// ThreadLocal與當前線程綁定, 即線程隔離, 並且使用了同一個DataSource作為key, 可以獲取到同一個ConnectionHolder
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); } }

 

 

四丶mybatis和spring的事務是如何結合使用的

   mybatis-spring項目,用於將Spring和mybatis整合

   mybatis源碼分析(三) mybatis-spring整合源碼分析

 

   mybatis-spring整合,需要配置SqlSessionFactoryBean構建生成SqlSessionFactory

   SqlSessionFactoryBean#buildSqlSessionFactory()

//如果為空,則使用默認的SpringManagedTransactionFactory生成SpringManagedTransaction
    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));

 

   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 {
// 從Spring transaction manager獲取之前由Spring獲取的connection
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"); }

 

  有本文第二節可知, mybatis執行事務相關操作, 如獲取Connection, 使用connection執行多條sql, 使用connection提交事務或者回滾事務, 都是委派給Transacation執行的,

要想將sql語句的執行由mybatis執行, 事務的提交或者回滾操作由Spring控制, 兩者需要關聯使用同一個connection, 在不同的方法中調用connection的相關方法操作,  (所以, Spring並沒有直接使用mybatis sqlSession中提供的提交或者回滾方法) . 如何安全的獲取同一個connection?這就需要使用TransactionSynchronizationManager

 

 

 

  ThreadLocal<Map<Object, Object>> resources

  保存connection資源,的 key為DataSource, value為ConnectionHolder

  (所以, 事務只支持在一個數據源中, 0.0)

 

 

學習資料:

      Spring事務原理分析

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM