深入理解MyBatis——查詢流程

MyBatis是目前非常流行的ORM框架,它的功能很強大,然而其實現卻比較簡單、優雅。本文主要講述MyBatis的架構設計思路,然後探究MyBatis的是如何實現查詢的。

MyBatis執行流程

利用MyBatis實現一次查詢

        InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");  
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();  
        //創建SqlSessionFactory
        SqlSessionFactory factory = builder.build(inputStream);  

        //從SqlSession工廠 SqlSessionFactory中創建一個SqlSession,進行數據庫操作  
        SqlSession sqlSession = factory.openSession();  

        //使用SqlSession查詢  
        Map<String,Object> params = new HashMap<String,Object>();  

        params.put("xxx",xxx);  
        //執行查詢
        List<Object> result = sqlSession.selectList("mapper.sqlId"params);
  1. SqlSession sqlSession = factory.openSession():開啓一個sqlSession,它表示和數據庫交互的會話,完成必要數據庫增刪改查功能。
  2. 執行sqlSession.selectList:返回查詢結果List,Mybatis和數據庫進行交互的流程都在此方法中。

那麼探究MyBtis就由此進入吧!

selectList(String statement, Object parameter)方法源碼:

public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
    //根據Statement Id,在mybatis 配置對象Configuration中查找和配置文件相對應的MappedStatement  
      MappedStatement ms = configuration.getMappedStatement(statement);
      //查找好以後交給executor.query方法執行下面的流程
      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
<select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" >  
  select   
    *  
    from table   
</select>  

說明:根據Statement獲得StatementId(如上面配置文件的Id爲selectByMinSalary),通過StatementId找出配置文件mapper.xml中對應的MappedStatement,並且將其交給executor的query方法來執行。

Executor的實現類BaseExecutor.query方法源碼

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //動態地生成需要執行的SQL語句,用BoundSql對象表示
    BoundSql boundSql = ms.getBoundSql(parameter);
    //設置一級緩存的key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

  @SuppressWarnings("unchecked")
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        //清空一級緩存
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
          //緩存中爲空就去數據庫查詢
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache(); // issue #482
      }
    }
    return list;
  }

//queryFromDatabase方法
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
    //真正的核心代碼
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    //將查詢結果放入緩存中
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }


//SimpleExecutor(BaseExecutor子類)類的doQuery()
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
    //獲得配置對象configuration
      Configuration configuration = ms.getConfiguration();
      //創建StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //StatementHandler根據configuration創建Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      //交給StatementHandler.query執行
      //成對數據庫的查詢,最終返回List結果集。
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

說明:Executor的功能和作用是
1.創建BoundSql;
2.創建緩存
3.將BoundSql傳遞給StatementHandler,由StatementHandler來完成對數據庫的查詢,以及返回List。

StatementHandler

接着我們進入prepareStatement方法設置參數:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    //對statement設置參數,就是對?佔位符賦值
    handler.parameterize(stmt);
    return stmt;
  }

進入parameterize方法:

public void parameterize(Statement statement) throws SQLException {  
    parameterHandler.setParameters((PreparedStatement) statement);  
  }  

此時我們知道,是通過使用ParameterHandler對象來完成對Statement的設值 ,將一個一個參數填充到”?”中。

此時Statement對象構建完成了,是一個可用的SQL語句了。

StatementHandler.query()方法:

//PreParedStatementHandler類來實現query
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//將statement轉爲PreparedStatement
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    //通過使用ResultHandler來處理ResultSet來返回List
    return resultSetHandler.<E> handleResultSets(ps);
  }

說明:StatementHandler通過ResultHandler的handleResultSets方法,實現將ResultSet結果集轉換成List 結果集。

ResultHandler

ResultHandler.handleResultSets用來將ResultSet結果集轉換成List

源碼如下

  //
  // HANDLE RESULT SETS
  //DefaultResultSetHandler的handleResultSets方法

  public List<Object> handleResultSets(Statement stmt) throws SQLException {
  //這裏是List
    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResulSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

總結

1.開啓一個sqlSession

2.sqlSession根據StatementId得到一個MapperStatement,並交給Executor來執行查詢

3.Executor得到MapperStatement後,會生成一個boundsql,並且創建一個代表緩存的cacheKey,如果對於緩存中有數據,則直接返回;如果緩存中沒有,就需要boundsql傳遞且藉助StatementHandler去數據庫中獲得數據,並返回結果集List,之後在加入緩存。

4.StatementHandle**r獲得boundsql以後,藉助**ParameterHandler 來講參數設置進Statement並返回一個PreparedStatement對象,此時執行查找返回結果集ResultSet。StatementHandler此時再借助ResultHandler.handleResultSets用來將ResultSet結果集轉換成List 。

借用大神的一張圖來概括上述流程:

這裏寫圖片描述

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