深入淺出MyBatis:MyBatis解析和運行原理

上一篇介紹了反射和動態代理基礎,主要是爲本篇文章做個鋪墊,反射使配置和靈活性大大提高,可以給很多配置設置參數,動態代理可以在運行時創建代理對象,做一些特殊的處理。

本篇會介紹MyBatis解析和運行原理,下一篇介紹插件及應用,目的是更好地編寫插件,通過本篇的介紹,你會瞭解到:

  • 構建SqlSessionFactory過程
  • 映射器的動態代理
  • SqlSession的4大對象
  • sql執行的過程

SqlSessionFactory和SqlSession是MyBatis的核心組件,在文章 JDBC和MyBatis介紹 中有詳細說明。

構建SqlSessionFactory過程

構建主要分爲2步:

  • 通過XMLConfigBuilder解析配置的XML文件,讀出配置參數,包括基礎配置XML文件和映射器XML文件;
  • 使用Configuration對象創建SqlSessionFactory,SqlSessionFactory是一個接口,提供了一個默認的實現類DefaultSqlSessionFactory。

說白了,就是將我們的所有配置解析爲Configuration對象,在整個生命週期內,可以通過該對象獲取需要的配置。

由於插件需要頻繁訪問映射器的內部組成,會重點這部分,瞭解這塊配置抽象出來的對象:

MappedStatement

它保存映射器的一個節點(select|insert|delete|update),包括配置的SQL,SQL的id、緩存信息、resultMap、parameterType、resultType等重要配置內容。

它涉及的對象比較多,一般不去修改它。

SqlSource

它是MappedStatement的一個屬性,主要作用是根據參數和其他規則組裝SQL,也是很複雜的,一般也不用修改它。

BoundSql

對於參數和SQL,主要反映在BoundSql類對象上,在插件中,通過它獲取到當前運行的SQL和參數以及參數規則,作出適當的修改,滿足特殊的要求。

BoundSql提供3個主要的屬性:parameterObject、parameterMappings和sql,下面分別來介紹。

parameterObject爲參數本身,可以傳遞簡單對象、POJO、Map或@Param註解的參數:

  • 傳遞簡單對象(int、float、String等),會把參數轉換爲對應的類,比如int會轉換爲Integer;
  • 如果傳遞的是POJO或Map,paramterObject就是傳入的POJO或Map不變;
  • 如果傳遞多個參數,沒有@Param註解,parameterObject就是一個Map<String,Object>對象,類似這樣的形式{"1":p1 , "2":p2 , "3":p3 ... "param1":p1 , "param2":p2 , "param3",p3 ...},所以在編寫的時候可以使用#{param1}或#{1}去引用第一個參數;
  • 如果傳遞多個參數,有@Param註解,與沒有註解的類似,只是將序號的key替換爲@Param指定的name;

parameterMappings,它是一個List,元素是ParameterMapping對象,這個對象會描繪sql中的參數引用,包括名稱、表達式、javaType、jdbcType、typeHandler等信息。

sql,是寫在映射器裏面的一條sql。

有了Configuration對象,構建SqlSessionFactory就簡單了:

sqlSessionFactory = new SqlSessionFactoryBuilder().bulid(inputStream);

SqlSession運行過程

映射器的動態代理

Mapper映射是通過動態代理來實現的,使用JDK動態代理返回一個代理對象,供調用者訪問。

首先看看實現InvocationHandler接口的類,它是執行本代理方法的關鍵,可以看到,Mapper是一個接口,會生成MapperMethod對象,調用execute方法。

public class MapperProxy<T> implements InvocationHandler, Serializable {

  .....

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

看下面的代碼,MapperMethod採用命令模式,根據不同的sql操作,做不同的處理。

public class MapperMethod {
  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;

        ......

      }
    }
  }

最後看下,生成代理類的方法,就是使用JDK動態代理Proxy來創建的。

public class MapperProxyFactory<T> {

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

}

總結下映射器的調用過程,返回的Mapper對象是代理對象,當調用它的某個方法時,其實是調用MapperProxy#invoke方法,而映射器的XML文件的命名空間對應的就是這個接口的全路徑,會根據全路徑和方法名,便能夠綁定起來,定位到sql,最後會使用SqlSession接口的方法使它能夠執行查詢。

SqlSession下的四大對象

通過上面的分析,映射器就是一個動態代理對象,進入到了MapperMethod的execute方法,它經過簡單的判斷就進入了SqlSession的刪除、更新、插入、選擇等方法,這些方法如何執行是下面要介紹的內容。

Mapper執行的過程是通過Executor、StatementHandler、ParameterHandler和ResultHandler來完成數據庫操作和結果返回的,理解他們是編寫插件的關鍵:

  • Executor:執行器,由它統一調度其他三個對象來執行對應的SQL;
  • StatementHandler:使用數據庫的Statement執行操作;
  • ParameterHandler:用於SQL對參數的處理;
  • ResultHandler:進行最後數據集的封裝返回處理;

在MyBatis中存在三種執行器:

  • SIMPLE:簡易執行器,默認的執行器;
  • REUSE:執行重用預處理語句;
  • BATCH:執行重用語句和批量更新,針對批量專用的執行器;

以SimpleExecutor爲例,說明執行過程

public class SimpleExecutor extends BaseExecutor {

  /**
  * 執行查詢操作
  */
  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 handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

  /**
  * 初始化StatementHandler
  */
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    handler.parameterize(stmt);
    return stmt;
  }

  /**
  * 執行查詢
  */
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleResultSets(statement);
  }
}

可以看到最後會委託給StatementHandler會話器進行處理,它是一個接口,實際創建的是RoutingStatementHandler對象,但它不是真實的服務對象,它是通過適配器模式找到對應的StatementHandler執行的。在MyBatis中,StatementHandler和Executor一樣分爲三種:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler。

Executor會先調用StatementHandler的prepare方法預編譯SQL語句,同時設置一些基本運行的參數。然後調用parameterize()方法啓用ParameterHandler設置參數,完成預編譯,跟着執行查詢,用ResultHandler封裝結果返回給調用者。

參數處理器和結果處理器比較簡單,就不在此介紹了。

下一篇會介紹插件及其應用,主要是在sql執行的過程中,在四大對象的基礎上進行擴展。

歡迎掃描下方二維碼,關注我的個人微信公衆號 ~

情情說

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章