• 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事务原理分析

     

    人生没有彩排,每一天都是现场直播
  • 相关阅读:
    leetcode刷题笔记303题 区域和检索
    leetcode刷题笔记301题 删除无效的括号
    20201208日报
    20201118日报
    20201117日报
    20201116日报
    20201115日报
    20201114日报
    20201113日报
    20201112日报
  • 原文地址:https://www.cnblogs.com/timfruit/p/11508873.html
Copyright © 2020-2023  润新知