1. 程式人生 > >Mybatis啟動流程詳解

Mybatis啟動流程詳解

今天,我擬從一個簡單的selectOne查詢入手,追蹤mybatis框架執行的足跡。

Mybatis整體流程圖

Mybatis整體流程圖

單元測試程式碼(selectOne型別)

    @Test
    public  void queryFinancialAccountTest(){
        FundFinancialExtDTO financialAccountExtPO = new FundFinancialExtDTO();
        // 部分程式碼略去
        FundAccAndExtDTO fundAccAndExtDTO = fundFinancialExtMapper.queryFinancialAccount(financialAccountExtPO);
    }

mybatis.xml配置

    <!-- Mybatis配置 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.roger.practice.dal.dao" />
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
>
<property name="dataSource" ref="dataSource" /> <property name="typeAliasesPackage" value="com.roger.practice.entity" /> <property name="mapperLocations" value="classpath*:mapper/**/*.xml" /> <property name="plugins"> <array> <bean
class="com.roger.practice.mybatis.page.interceptor.PageInterceptor" />
<bean class="com.roger.practice.mybatis.page.interceptor.PageSqlRewriteInterceptor"> <property name="dialect" value="oracle" /> </bean> </array> </property> </bean>

初始化部分 SqlSessionFactoryBuilder

Mybatis整合在Spring中,方法afterPropertiesSet()將在所有的屬性被初始化後被呼叫。檢視org.mybatis.spring.SqlSessionFactoryBean類中的afterPropertiesSet()方法,發現該方法開始建立SqlSessionFactory例項

    @Override
    public void afterPropertiesSet() throws Exception {
        // 略去程式碼請參考原始碼
        this.sqlSessionFactory = buildSqlSessionFactory();
    }

    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
        // 略去程式碼將物件工廠objectFactory,物件包裝工廠objectWrapperFactory,類型別名typeAliasesPackage/typeAliases,外掛plugins,型別處理器typeHandlersPackage/typeHandlers,快取cache,環境environments,事務工廠transactionFactory等資訊配置存到Configuration物件中。
        // 通過xmlMapperBuilder來解析mapper檔案
        if (!isEmpty(this.mapperLocations)) {
          for (Resource mapperLocation : this.mapperLocations) {
            if (mapperLocation == null) {
              continue;
            }

            try {
              XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                  configuration, mapperLocation.toString(), configuration.getSqlFragments());
              xmlMapperBuilder.parse();
            } catch (Exception e) {
              throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
            } finally {
              ErrorContext.instance().reset();
            }

            if (LOGGER.isDebugEnabled()) {
              LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
            }
          }
        } else {
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
          }
        }
        return this.sqlSessionFactoryBuilder.build(configuration);
    }

org.apache.ibatis.session.SqlSessionFactoryBuilder提供了9種構造SqlSessionFactory的方法,但最終都要呼叫包含Configuration物件的構造方法,其通過載入配置檔案構造SqlSessionFactory物件、返回DefaultSqlSessionFactory物件。

9種構造SqlSessionFactory的方法

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

org.apache.ibatis.binding.MapperProxyFactory
Spring負責建立SqlSessionTemplate,執行getMapper方法時會建立動態代理,代理Test用例中的FundFinancialExtMapper介面。

  @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }

SelectOne查詢流程

基本查詢流程

org.apache.ibatis.binding.MapperProxy
Mybatis初始化載入的時候,利用MapperProxy代理了自己的Mapper介面類,生成一個代理處理類。代理處理的邏輯都在invoke方法裡,它根據目標類的介面(本例是FundFinancialExtMapper)生成 MapperMethod。sqlSession是由spring負責生成的SqlSessionTemplate,它是spring連線mybatis的模板類。接下來呼叫MapperMethod的execute方法就能獲取執行結果。

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

org.apache.ibatis.binding.MapperMethod
MapperMethod就像是一個分發者,它根據SqlCommandType,並獲取執行引數commandName和param,交由SqlSessionTemplate物件執行具體的操作。這樣mapper物件與sqlSession就真正的關聯起來了。本例中,將執行SqlSessionTemplate類中的selectOne方法。

        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);

org.mybatis.spring.SqlSessionTemplate
SqlSessionTemplate的實際執行是交給它的代理類完成的。檢視SqlSessionTemplate建構函式可知,它是由內部類SqlSessionInterceptor動態代理的,所有的處理邏輯都是在invoke方法裡。invoke方法裡執行了:
1. 通過靜態方法SqlSessionUtils.getSqlSession建立sqlSession,實際返回DefaultSqlSession物件。
2. DefaultSqlSession執行selectOne方法。
3. 執行成功則提交,出現異常則關閉sqlSession。

  // SqlSessionTemplate的建構函式
  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }
  // 內部類SqlSessionInterceptor
  private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        // 略去程式碼請參考原始碼
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

org.apache.ibatis.executor.Executor
sqlSession只是一個門面,真正發揮作用的是executor,對sqlSession方法的訪問最終都會落到executor的相應方法上去。
executor物件是執行openSessionFromDataSource方法時建立的,見org.apache.ibatis.session.Configuration裡的newExecutor方法。executor具體實現是SimpleExecutor,由於cacheEnabled預設為ture,還追加了快取功能。另外它還可以追加攔截器。

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      // 實現快取
      executor = new CachingExecutor(executor);
    }
    // 追加攔截器,比如分頁攔截器
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

org.apache.ibatis.executor.CachingExecutor
query方法會最終委派org.apache.ibatis.executor.SimpleExecutor類中的doQuery方法。query方法如下:

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

org.apache.ibatis.executor.SimpleExecutor
SimpleExecutor的doQuery方法是具體的實現。

  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // StatementHandler的建立與executor的實現很相似,又是在Configuration裡通過newStatementHandler方法建立的。
      // 由org.apache.ibatis.executor.statement.RoutingStatementHandler代理實際的StatementHandler實現。
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 對查詢語句進行預編譯,並解析引數實體。
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 方法執行時改由PreparedStatementHandler實際代理進行查詢
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

通過StatementType獲取具體的StatementHandler類,從預設配置可知,PreparedStatementHandler是實際代理的物件。其中構造StatementHandler的時候,檢視BaseStatementHandler建構函式可知,ParameterHandler和ResultSetHandler也是有Configuration生成的,同樣也可追加攔截器。

org.apache.ibatis.executor.statement.PreparedStatementHandler
PreparedStatementHandler的父類是BaseStatementHandler,BaseStatementHandler的建構函式是有這麼一段:

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

它觸發了sql 的解析,在解析sql的過程中,TypeHandler也被決斷出來了,決斷的原則就是根據引數的型別和引數對應的JDBC型別決定使用哪個TypeHandler。比如:引數型別是String的話就用StringTypeHandler,引數型別是整數的話就用IntegerTypeHandler等。

query方法如下,它執行了execute方法並完成結果集的對映。

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 實際執行在此處那!!!
    ps.execute();
    // 由org.apache.ibatis.executor.resultset.DefaultResultSetHandler代理實現
    // 完成結果集的對映
    return resultSetHandler.<E> handleResultSets(ps);
  }

org.apache.ibatis.scripting.defaults.DefaultParameterHandler
setParameters方法用來解析引數實體,其中propertyName獲取引數名,value是引數值,typeHandler和jdbcType是引數型別。

org.apache.ibatis.executor.resultset.DefaultResultSetHandler
完成結果集的對映。

org.apache.ibatis.session.SqlSessionFactory
SqlSessionFactory作為SqlSession的工廠,提供了8種獲取SqlSession的方法,同時還提供了獲取Configuration的方法。
8種獲取SqlSession的方法
8種獲取SqlSession的方法主要涉及4個引數:是否自動提交、自定義Connection、事務級別、ExecutorType(Statement型別【普通、預處理、批處理】)。包含Connection型別引數的方法會呼叫openSessionFromConnection方法,其它都會呼叫openSessionFromDataSource方法,最終都返回DefaultSqlSession物件。

org.apache.ibatis.session.defaults.DefaultSqlSessionFactory是SqlSessionFactory介面的實現,以openSessionFromDataSource方法為例,建立sqlsession經過了以下幾個主要步驟:
1) 從配置中獲取Environment;
2) 根據Environment建立事務工廠TransactionFactory;
3) 從Environment中取得DataSource、進而建立事務物件Transaction;
4) 建立Executor物件;
5) 建立sqlsession物件。

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      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 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;
      }      
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      final Transaction tx = transactionFactory.newTransaction(connection);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

org.apache.ibatis.session.SqlSession
這裡寫圖片描述

org.apache.ibatis.session.defaults.DefaultSqlSession
DefaultSqlSession實現了SqlSession介面,主要封裝了Configuration物件、Executor物件、是否自動提交。

  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

  public DefaultSqlSession(Configuration configuration, Executor executor) {
    // 預設不自動提交
    this(configuration, executor, false);
  }

它利用自己封裝的一套東西,還包括Executor(封裝Statement)、ResultHandler(封裝處理ResultSet物件)、RowBounds(封裝分頁物件),提供了CRUD、提供了快取機制、提供了根據配置檔案獲取Sql語句的方法,提供了事務的提交和回滾等。

總結

大體流程就是:
1. 載入XML配置檔案建立Configuration物件完成初始化,建立並使用SqlSessionFactory物件。
2. 利用MapperProxy代理具體的Mapper介面類,生成了MapperMethod。
3. Spring負責生成SqlSessionTemplate,它實際由SqlSessionInterceptor動態代理,所有的處理邏輯都是在 invoke方法裡,主要是獲取SqlSession、生成可帶快取可追加外掛的Executor,並執行操作。DefaultSqlSession由SimpleExecutor靜態代理執行查詢操作。
4. RoutingStatementHandler根據配置Statement型別建立真正執行資料庫操作的StatementHandler,實際由PreparedStatementHandler進行查詢語句的預編譯、查詢引數實體解析、執行查詢。
5. DefaultResultSetHandler完成結果集的對映。