zoukankan      html  css  js  c++  java
  • Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?

    不知道一些同学有没有这种疑问,为什么Mybtis中要配置dataSource,Spring的事务中也要配置dataSource?那么Mybatis和Spring事务中用的Connection是同一个吗?我们常用配置如下

    <!--会话工厂 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource" />
    </bean>
    
    <!--spring事务管理 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource" />
    </bean>
    
    <!--使用注释事务 -->
    <tx:annotation-driven  transaction-manager="transactionManager" />

    看到没,sqlSessionFactory中配置了dataSource,transactionManager也配置了dataSource,我们来回忆一下SqlSessionFactoryBean这个类

     1 protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
     2 
     3     // 配置类
     4    Configuration configuration;
     5     // 解析mybatis-Config.xml文件,
     6     // 将相关配置信息保存到configuration
     7    XMLConfigBuilder xmlConfigBuilder = null;
     8    if (this.configuration != null) {
     9      configuration = this.configuration;
    10      if (configuration.getVariables() == null) {
    11        configuration.setVariables(this.configurationProperties);
    12      } else if (this.configurationProperties != null) {
    13        configuration.getVariables().putAll(this.configurationProperties);
    14      }
    15     //资源文件不为空
    16    } else if (this.configLocation != null) {
    17      //根据configLocation创建xmlConfigBuilder,XMLConfigBuilder构造器中会创建Configuration对象
    18      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
    19      //将XMLConfigBuilder构造器中创建的Configuration对象直接赋值给configuration属性
    20      configuration = xmlConfigBuilder.getConfiguration();
    21    } 
    22    
    23     //略....
    24 
    25    if (xmlConfigBuilder != null) {
    26      try {
    27        //解析mybatis-Config.xml文件,并将相关配置信息保存到configuration
    28        xmlConfigBuilder.parse();
    29        if (LOGGER.isDebugEnabled()) {
    30          LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
    31        }
    32      } catch (Exception ex) {
    33        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
    34      }
    35    }
    36     
    37    if (this.transactionFactory == null) {
    38      //事务默认采用SpringManagedTransaction,这一块非常重要
    39      this.transactionFactory = new SpringManagedTransactionFactory();
    40    }
    41     // 为sqlSessionFactory绑定事务管理器和数据源
    42     // 这样sqlSessionFactory在创建sqlSession的时候可以通过该事务管理器获取jdbc连接,从而执行SQL
    43    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
    44     // 解析mapper.xml
    45    if (!isEmpty(this.mapperLocations)) {
    46      for (Resource mapperLocation : this.mapperLocations) {
    47        if (mapperLocation == null) {
    48          continue;
    49        }
    50        try {
    51          // 解析mapper.xml文件,并注册到configuration对象的mapperRegistry
    52          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
    53              configuration, mapperLocation.toString(), configuration.getSqlFragments());
    54          xmlMapperBuilder.parse();
    55        } catch (Exception e) {
    56          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
    57        } finally {
    58          ErrorContext.instance().reset();
    59        }
    60 
    61        if (LOGGER.isDebugEnabled()) {
    62          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
    63        }
    64      }
    65    } else {
    66      if (LOGGER.isDebugEnabled()) {
    67        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
    68      }
    69    }
    70 
    71     // 将Configuration对象实例作为参数,
    72     // 调用sqlSessionFactoryBuilder创建sqlSessionFactory对象实例
    73    return this.sqlSessionFactoryBuilder.build(configuration);
    74 }

    我们看第39行,Mybatis集成Spring后,默认使用的transactionFactory是SpringManagedTransactionFactory,那我们就来看看其获取Transaction的方法

    private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
        try {
          boolean autoCommit;
          try {
            autoCommit = connection.getAutoCommit();
          } catch (SQLException e) {
            // Failover to true, as most poor drivers
            // or databases won't support transactions
            autoCommit = true;
          }      
          //从configuration中取出environment对象
          final Environment environment = configuration.getEnvironment();
          //从environment中取出TransactionFactory
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          //创建Transaction
          final Transaction tx = transactionFactory.newTransaction(connection);
          //创建包含事务操作的执行器
          final Executor executor = configuration.newExecutor(tx, execType);
          //构建包含执行器的SqlSession
          return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
    }
    
    private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
        if (environment == null || environment.getTransactionFactory() == null) {
          return new ManagedTransactionFactory();
        }
        //这里返回SpringManagedTransactionFactory
        return environment.getTransactionFactory();
    }
    
    @Override
    public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        //创建SpringManagedTransaction
        return new SpringManagedTransaction(dataSource);
    }

    SpringManagedTransaction

    也就是说mybatis的执行事务的事务管理器就切换成了SpringManagedTransaction,下面我们再去看看SpringManagedTransactionFactory类的源码:

    public class SpringManagedTransaction implements Transaction {
        private static final Log LOGGER = LogFactory.getLog(SpringManagedTransaction.class);
        private final DataSource dataSource;
        private Connection connection;
        private boolean isConnectionTransactional;
        private boolean autoCommit;
    
        public SpringManagedTransaction(DataSource dataSource) {
            Assert.notNull(dataSource, "No DataSource specified");
            this.dataSource = dataSource;
        }
    
        public Connection getConnection() throws SQLException {
            if (this.connection == null) {
                this.openConnection();
            }
    
            return this.connection;
        }
    
        private void openConnection() throws SQLException {
            //通过DataSourceUtils获取connection,这里和JdbcTransaction不一样
            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");
            }
    
        }
    
        public void commit() throws SQLException {
            if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Committing JDBC Connection [" + this.connection + "]");
                }
                //通过connection提交,这里和JdbcTransaction一样
                this.connection.commit();
            }
    
        }
    
        public void rollback() throws SQLException {
            if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]");
                }
                //通过connection回滚,这里和JdbcTransaction一样
                this.connection.rollback();
            }
    
        }
    
        public void close() throws SQLException {
            DataSourceUtils.releaseConnection(this.connection, this.dataSource);
        }
    
        public Integer getTimeout() throws SQLException {
            ConnectionHolder holder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.dataSource);
            return holder != null && holder.hasTimeout() ? holder.getTimeToLiveInSeconds() : null;
        }
    }

    org.springframework.jdbc.datasource.DataSourceUtils#getConnection

    public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
        try {
            return doGetConnection(dataSource);
        }
        catch (SQLException ex) {
            throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
        }
    }
    
    public static Connection doGetConnection(DataSource dataSource) throws SQLException {
        Assert.notNull(dataSource, "No DataSource specified");
        //TransactionSynchronizationManager重点!!!有没有很熟悉的感觉??
        //还记得我们前面Spring事务源码的分析吗?@Transaction会创建Connection,并放入ThreadLocal中
        //这里从ThreadLocal中获取ConnectionHolder
        ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
        if (conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) {
            logger.debug("Fetching JDBC Connection from DataSource");
            //如果没有使用@Transaction,那调用Mapper接口方法时,也是通过Spring的方法获取Connection
            Connection con = fetchConnection(dataSource);
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
                logger.debug("Registering transaction synchronization for JDBC Connection");
                ConnectionHolder holderToUse = conHolder;
                if (conHolder == null) {
                    holderToUse = new ConnectionHolder(con);
                } else {
                    conHolder.setConnection(con);
                }
    
                holderToUse.requested();
                TransactionSynchronizationManager.registerSynchronization(new DataSourceUtils.ConnectionSynchronization(holderToUse, dataSource));
                holderToUse.setSynchronizedWithTransaction(true);
                if (holderToUse != conHolder) {
                    //将获取到的ConnectionHolder放入ThreadLocal中,那么当前线程调用下一个接口,下一个接口使用了Spring事务,那Spring事务也可以直接取到Mybatis创建的Connection
                    //通过ThreadLocal保证了同一线程中Spring事务使用的Connection和Mapper代理类使用的Connection是同一个
                    TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
                }
            }
    
            return con;
        } else {
            conHolder.requested();
            if (!conHolder.hasConnection()) {
                logger.debug("Fetching resumed JDBC Connection from DataSource");
                conHolder.setConnection(fetchConnection(dataSource));
            }
    
            //所以如果我们业务代码使用了@Transaction注解,在Spring中就已经通过dataSource创建了一个Connection并放入ThreadLocal中
            //那么当Mapper代理对象调用方法时,通过SqlSession的SpringManagedTransaction获取连接时,就直接获取到了当前线程中Spring事务创建的Connection并返回
            return conHolder.getConnection();
        }
    }

    想看怎么获取connHolder 

    org.springframework.transaction.support.TransactionSynchronizationManager#getResource

    //保存数据库连接的ThreadLocal
    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
    @Nullable
    public static Object getResource(Object key) {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        //获取ConnectionHolder
        Object value = doGetResource(actualKey);
        ....
        return value;
    }
    
    @Nullable
    private static Object doGetResource(Object actualKey) {
        /**
         * 从threadlocal <Map<Object, Object>>中取出来当前线程绑定的map
         * map里面存的是<dataSource,ConnectionHolder>
         */
        Map<Object, Object> map = resources.get();
        if (map == null) {
            return null;
        }
        //map中取出来对应dataSource的ConnectionHolder
        Object value = map.get(actualKey);
        // Transparently remove ResourceHolder that was marked as void...
        if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
            map.remove(actualKey);
            // Remove entire ThreadLocal if empty...
            if (map.isEmpty()) {
                resources.remove();
            }
            value = null;
        }
        return value;
    }

    我们看到直接从ThreadLocal中取出来的conn,而spring自己的事务也是操作的这个ThreadLocal中的conn来进行事务的开启和回滚,由此我们知道了在同一线程中Spring事务中的Connection和Mybaits中Mapper代理对象中操作数据库的Connection是同一个,当取出来的conn为空时候,调用org.springframework.jdbc.datasource.DataSourceUtils#fetchConnection获取,然后把从数据源取出来的连接返回

    private static Connection fetchConnection(DataSource dataSource) throws SQLException {
        //从数据源取出来conn
        Connection con = dataSource.getConnection();
        if (con == null) {
            throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);
        }
        return con;
    }

    我们再来回顾一下上篇文章中的SqlSessionInterceptor

     1 private class SqlSessionInterceptor implements InvocationHandler {
     2     private SqlSessionInterceptor() {
     3     }
     4 
     5     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     6         SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
     7 
     8         Object unwrapped;
     9         try {
    10             Object result = method.invoke(sqlSession, args);
    11             // 如果当前操作没有在一个Spring事务中,则手动commit一下
    12             // 如果当前业务没有使用@Transation,那么每次执行了Mapper接口的方法直接commit
    13             // 还记得我们前面讲的Mybatis的一级缓存吗,这里一级缓存不能起作用了,因为每执行一个Mapper的方法,sqlSession都提交了
    14             // sqlSession提交,会清空一级缓存
    15             if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
    16                 sqlSession.commit(true);
    17             }
    18 
    19             unwrapped = result;
    20         } catch (Throwable var11) {
    21             unwrapped = ExceptionUtil.unwrapThrowable(var11);
    22             if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
    23                 SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
    24                 sqlSession = null;
    25                 Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
    26                 if (translated != null) {
    27                     unwrapped = translated;
    28                 }
    29             }
    30 
    31             throw (Throwable)unwrapped;
    32         } finally {
    33             if (sqlSession != null) {
    34                 SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
    35             }
    36 
    37         }
    38         return unwrapped;
    39     }
    40 }

    看第15和16行,如果我们没有使用@Transation,Mapper方法执行完后,sqlSession将会提交,也就是说通过org.springframework.jdbc.datasource.DataSourceUtils#fetchConnection获取到的Connection将会commit,相当于Connection是自动提交的,也就是说如果不使用@Transation,Mybatis将没有事务可言。

    Mybatis和Spring整合后SpringManagedTransaction和Spring的Transaction的关系:

    • 如果开启Spring事务,则先有Spring的Transaction,然后mybatis创建sqlSession时,会创建SpringManagedTransaction并加入sqlSession中,SpringManagedTransaction中的connection会从Spring的Transaction创建的Connection并放入ThreadLocal中获取
    • 如果没有开启Spring事务或者第一个方法没有事务后面的方法有事务,则SpringManagedTransaction创建Connection并放入ThreadLocal中

    spring结合mybatis后mybaits一级缓存失效分为两种情况:

    • 如果没有开启事务,每一次sql都是用的新的SqlSession,这时mybatis的一级缓存是失效的。
    • 如果有事务,同一个事务中相同的查询使用的相同的SqlSessioon,此时一级缓存是生效的。

    如果使用了@Transation呢?那在调用Mapper代理类的方法之前就已经通过Spring的事务生成了Connection并放入ThreadLocal,并且设置事务不自动提交,当前线程多个Mapper代理对象调用数据库操作方法时,将从ThreadLocal获取Spring创建的connection,在所有的Mapper方法调用完后,Spring事务提交或者回滚,到此mybatis的事务是怎么被spring管理的就显而易见了

    还有文章开头的问题,为什么Mybtis中要配置dataSource,Spring的事务中也要配置dataSource?

    因为Spring事务在没调用Mapper方法之前就需要开一个Connection,并设置事务不自动提交,那么transactionManager中自然要配置dataSource。那如果我们的Service没有用到Spring事务呢,难道就不需要获取数据库连接了吗?当然不是,此时通过SpringManagedTransaction调用org.springframework.jdbc.datasource.DataSourceUtils#getConnection#fetchConnection方法获取,并将dataSource作为参数传进去,实际上获取的Connection都是通过dataSource来获取的。

  • 相关阅读:
    服务器的计时器的Interval最大能设置为多大?
    IE页面嵌入复杂WinForm控件不能正常激活
    自动升级组件居然把我那提供升级的站点目录整个删除了!
    自动升级失败,缺少System.resources.dll?
    字频分析结果出来了
    原来是2003 Server的问题
    一直这么渴望执着不一定能成功,只是活着就是执着
    模拟生态系统自己编写"生命"
    微软的Soap样例是错误的!
    对哈希表进行排序
  • 原文地址:https://www.cnblogs.com/java-chen-hao/p/11839993.html
Copyright © 2011-2022 走看看