zoukankan      html  css  js  c++  java
  • Mybatis——Spring事务实现

    前面学习Mybatis中Executor执行sql时,只研究了非事务执行时,直接DataSource.getConnection()创建连接。这里学习一下事务执行。

    前提:SpringAOP——事务实现中学习到:Spring开启事务时会创建一个“事务连接”(需要手动提交的连接)然后通过ThreadLocal的方式,存储到当前线程中。

    起因是面试官问了一个问题:spring中的事务连接和mybatis中的事务连接是否是同一个?

    回答说:是同一个,但是说不出具体实现来(ThreadLocal)。

    也就是说“事务连接”是Spring中创建的,Mybatis执行sql时,会从线程中取出“事务连接”,然后创建statement执行。结合前面学习的mybatis源码,用源码求证一下

     一、复习mybatis与spring的交界处——代理实现

     MapperFactoryBean.getObject()创建了一个接口的代理实例,InvocationHandler是MapperProxy类型,方法调用代码如下:

    /* org.apache.ibatis.binding.MapperProxy#invoke */
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          if (Object.class.equals(method.getDeclaringClass())) {
            //Object的方法直接执行mapperProxy的方法
            return method.invoke(this, args);
          } else if (isDefaultMethod(method)) {
            //静态方法直接执行
            return invokeDefaultMethod(proxy, method, args);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
        //new MapperMethod(mapperInterface, method, configuration)
        //实际new sqlCommond(mapperInterface, mehod, configuration)
        //sqlCommond.name = mapperStatement.getId
        //sqlCommond.type = ms.getSqlCommondType()--insert|delete|update|select
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        //sqlSession根据sqlCommondType执行对应sql,例如当sqlCommondType == select时
        //sqlsession.select(sqlCommond.name)
        return mapperMethod.execute(sqlSession, args);
      }
    
    /* org.apache.ibatis.binding.MapperMethod#execute */
      public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
          case INSERT: {
          Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
          }
          case UPDATE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
          }
          case DELETE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
          }
          case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
              executeWithResultHandler(sqlSession, args);
              result = null;
            } else if (method.returnsMany()) {
              result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
              result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
              result = executeForCursor(sqlSession, args);
            } else {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = sqlSession.selectOne(command.getName(), param);
            }
            break;
          case FLUSH:
            result = sqlSession.flushStatements();
            break;
          default:
            throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
          throw new BindingException("Mapper method '" + command.getName() 
              + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
      }

    二、mybatis中事务实现

    以上面的insert为例,result = rowCountResult(sqlSession.insert(command.getName(), param));

    sqlSession.insert()-->BaseExecutor.update()-->SimpleExecutor.doUpdate()

    /* org.apache.ibatis.executor.SimpleExecutor#doUpdate */
      public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {
          Configuration configuration = ms.getConfiguration();
          StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
          //这里获取连接并创建statement
          stmt = prepareStatement(handler, ms.getStatementLog());
          return handler.update(stmt);
        } finally {
          closeStatement(stmt);
        }
      }
    
      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 {
        //获取连接transaction.getconnection()
        Connection connection = transaction.getConnection();
        if (statementLog.isDebugEnabled()) {
          return ConnectionLogger.newInstance(connection, statementLog, queryStack);
        } else {
          return connection;
        }
      }

    重点是BaseExecutor初始化时,transaction属性的类型。

    /* org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource */
      private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
          final Environment environment = configuration.getEnvironment();
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          //transaction属性
          tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
          final Executor executor = configuration.newExecutor(tx, execType);
          return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
          closeTransaction(tx); // may have fetched a connection so lets call close()
          throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }
    
      private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
        //需要确定environment.getTransactionFactory的类型
        if (environment == null || environment.getTransactionFactory() == null) {
          return new ManagedTransactionFactory();
        }
        return environment.getTransactionFactory();
      }

    确定environment.transactionFactory属性:SpringManagedTransactionFactory类型的实例

    /* org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory */
      protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    
        Configuration configuration;
    
        //...
    
        if (this.transactionFactory == null) {
          this.transactionFactory = new SpringManagedTransactionFactory();
        }
    
        //transactionFactory是SpringManagedTransactionFactory类型实例
        configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
    
        //...
    
        return this.sqlSessionFactoryBuilder.build(configuration);
      }

    所以transaction是SpringManagedTransaction类型实例

    /* org.mybatis.spring.transaction.SpringManagedTransactionFactory#newTransaction(javax.sql.DataSource, org.apache.ibatis.session.TransactionIsolationLevel, boolean) */
      public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        return new SpringManagedTransaction(dataSource);
      }

    Mybatis事务最终获取连接:SpringManagedTransaction.getConnection()

    /* org.mybatis.spring.transaction.SpringManagedTransaction#getConnection */
      public Connection getConnection() throws SQLException {
        if (this.connection == null) {
          openConnection();
        }
        return this.connection;
      }
    
      private void openConnection() throws SQLException {
        this.connection = DataSourceUtils.getConnection(this.dataSource);
        this.autoCommit = this.connection.getAutoCommit();
        this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
    
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug(
              "JDBC Connection ["
                  + this.connection
                  + "] will"
                  + (this.isConnectionTransactional ? " " : " not ")
                  + "be managed by Spring");
        }
      }
    /* org.springframework.jdbc.datasource.DataSourceUtils#getConnection */
        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());
            }
        }
    
        public static Connection doGetConnection(DataSource dataSource) throws SQLException {
            Assert.notNull(dataSource, "No DataSource specified");
            //从当前线程中获取连接
            //connectionHolder = Thread.currentThread().get("Transactinal resources").get(dataSource)
            ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
            if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
                conHolder.requested();
                if (!conHolder.hasConnection()) {
                    //不是事务连接时,从DataSource获取一个连接
                    logger.debug("Fetching resumed JDBC Connection from DataSource");
                    conHolder.setConnection(fetchConnection(dataSource));
                }
                //事务执行时,从Thread中获取spring创建的事务连接
                //非事务执行时,直接从Datasource获取一个连接
                return conHolder.getConnection();
            }
            // Else we either got no holder or an empty thread-bound holder here.
            //获得一个没有事务连接的空封装器ConnectionHolder时,从DataSource获取一个连接
            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;
        }

    验证完毕:

    “事务连接”是Spring调用DataSource创建的,并放到线程中,Mybatis执行sql时,会从线程中取出“事务连接”,然后创建statement执行。(ThreadLocal实现)

    “非事务连接”由mybatis直接调用DataSource创建。

    补充:mybatis的两种config文件,需要注意传统配置文件,配置可能导致spring事务失效。

    ① 依赖mybatis-spring.jar后的配置文件,默认初始化SQLSessionFactoryBean时,初始化SpringManagedTransactionFactory放入到Environment中

        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"/>
            <property name="mapperLocations" value="mapper/UserMapper.xml"/>
        </bean>

    ② 传统的配置文件,<environment />有个属性<transactionManager />与上面transactionFactory对应,需要设置成SpringManagedTransactionFactory.class,否则Spring事务无效

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <!-- 配置文件的根元素 -->
    <configuration>
            <!-- ... -->
        <!-- 环境:配置mybatis的环境 -->
        <environments default="dev">
            <!-- 环境变量:可以配置多个环境变量,比如使用多数据源时,就需要配置多个环境变量 -->
            <environment id="dev">
                <!-- 事务管理器 -->
                <!-- "JDBC" == JdbcTransactionFactory.class 直接从datasource获取连接,会导致spring事务失效-->
                <transactionManager type="JDBC"></transactionManager>
                <!-- 数据源 -->
                <dataSource type="POOLED">
                    <property name="driver" value="${mysql.driver}"/>
                    <property name="url" value="${mysql.url}"/>
                    <property name="username" value="${mysql.username}"/>
                    <property name="password" value="${mysql.password}"/>
                </dataSource>
            </environment>
        </environments>
        <!-- ... -->
    </configuration>
  • 相关阅读:
    进程和程序
    linux socket基本知识
    window核心编程 第五章 作业
    树的基本操作(C语言)
    每天都在反省自己,但是每天却都浑浑噩噩
    Windows核心编程 内核对象
    还没完整看过一本技术的书籍啊
    管道
    Memory Layout of a C Program(7.6)
    cpio命令用法
  • 原文地址:https://www.cnblogs.com/wqff-biubiu/p/12546674.html
Copyright © 2011-2022 走看看