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>
  • 相关阅读:
    Codeforces Beta Round #92 (Div. 2 Only) B. Permutations 模拟
    POJ 3281 Dining 最大流 Dinic算法
    POJ 2441 Arrange the BUlls 状压DP
    URAL 1152 Faise Mirrors 状压DP 简单题
    URAL 1039 Anniversary Party 树形DP 水题
    URAL 1018 Binary Apple Tree 树形DP 好题 经典
    pytorch中的forward前向传播机制
    .data()与.detach()的区别
    Argparse模块
    pytorch代码调试工具
  • 原文地址:https://www.cnblogs.com/wqff-biubiu/p/12546674.html
Copyright © 2011-2022 走看看