mybatis源碼分析4 - sqlSession讀寫數據庫完全解析

1 引言和主要類

創建完sqlSession實例後,我們就可以進行數據庫操作了。比如通過selectOne()方法查詢數據庫,如代碼

// 讀取XML配置文件
String resource = "main/resources/SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 創建sqlSessionFactory單例,初始化mybatis容器
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 創建sqlSession實例,用它來進行數據庫操作,mybatis運行時的門面
SqlSession session = sessionFactory.openSession();
// 進行數據庫查詢操作
User user = session.selectOne("test.findUserById", 1);  // 訪問數據庫,statement爲mapper.xml中的id
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

創建sqlSessionFactory單例和sqlSession實例在前兩節中分析過了,下面我們着重來分析sqlSession操作數據庫的過程。

sqlSession操作數據庫有兩種方法,可以直接使用select update insert delete等方法;也可以通過getMapper()先獲取mapper動態代理實例,然後再進行數據庫操作。相比而言mapper方式更靈活且不易出錯,是mybatis推薦的方式。本節我們分析直接使用select等方法的流程,下一節再分析mapper方式。

本節以selectOne()方法的實現過程爲例來分析sqlSession操作數據庫的流程,涉及的主要類如下。

  1. DefaultSqlSession:SqlSession的默認實現,其方法基本都是利用Executor代理實現。
  2. Executor:mybatis運行的核心,調度器。調度mybatis其他三大組件的執行。
  3. StatementHandler:SQL語句執行器,cache的管理等
  4. ParameterHandler:入參處理器,statementType爲PREPARE是需要使用到它,來解析入參到preparedStatement中
  5. ResultSetHandler:結果集映射處理器,將數據庫操作原始結果(主要是查詢操作),映射爲Java POJO。這正是ORM要解決的關鍵問題。

2 流程

2.1 DefaultSqlSession的selectOne()

先從DefaultSqlSession的selectOne()方法看起。

public <T> T selectOne(String statement, Object parameter) {
  // selectOne本質上是調用selectList實現,如果結果集大於一個,則報TooManyResultsException。
  List<T> list = this.<T>selectList(statement, parameter);
  if (list.size() == 1) {
    return list.get(0);
  } else if (list.size() > 1) {
    throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
  } else {
    return null;
  }
}

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

  // 由sql語句的標示statement和入參parameter,查詢滿足條件的數據列表
  // @Param statement: mapper.xml中mapper節點下的select delete update insert等子節點的id屬性
  // @Param parameter: 傳入sql語句的入參
  // @Param rowBounds: 邏輯分頁,包含offset和limit兩個主要成員變量。mybatis分頁邏輯爲捨棄offset之前條目,取剩下的limit條。默認DEFAULT不分頁
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      // 從mappers節點初始化階段創建好的mappedStatements這個Map中,找到key爲當前要找到的sql的id的那條
      MappedStatement ms = configuration.getMappedStatement(statement);

      // 通過執行器Executor作爲總調度來執行查詢語句,後面以BaseExecutor來分析。
      // BatchExecutor ReuseExecutor SimpleExecutor均繼承了BaseExecutor
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

selectOne()方法其實是通過調用selectList()實現的,因爲二者本質是完全相同的,只是前者返回一個對象,而後者爲列表List而已。selectList()採用代理模式,使用調度器Executor的query()方法實現。上一節着重講過Executor是SqlSession各個方法的具體實現,是mybatis運行的核心,通過調度StatementHandler ParameterHandler ResultSetHandler三個組件來完成sqlSession操作數據庫的整個過程。Executor的實現類有SimpleExecutor ReuseExecutor BatchExecutor等,它們的基類都是BaseExecutor。下面來分析BaseExecutor的query

2.2 BaseExecutor的query() 調度器開始數據庫query

// BaseExecutor的查找方法
 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
   // 從MappedStatement中找到boundSql成員變量,前面SqlSessionFactory創建部分講到過Mapper解析時的三大組件:MappedStatement SqlSource BoundSql
   // 其中BoundSql通過sql執行語句和入參,來組裝最終查詢數據庫用到的sql。
   BoundSql boundSql = ms.getBoundSql(parameter);

   // 創建CacheKey,用作緩存的key,不用深入理解。
   // sql的id,邏輯分頁rowBounds的offset和limit,boundSql的sql語句均相同時(主要是動態sql的存在),也就是組裝後的SQL語句完全相同時,才認爲是同一個cacheKey
   CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);

   // 真正的查詢語句執行處,關鍵代碼
   return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

// 查找方法
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());
    // 調度器已經close了則報錯
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }

    // flush cache, 即寫入並清空cache。之後就只能從數據庫中讀取了,這樣可以防止髒cache
    // localCache和localOutputParameterCache爲BaseExecutor的成員變量,它們構成了mybatis的一級緩存,也就是sqlSession級別的緩存,默認是開啓的。
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }

    // 從緩存或數據庫中查詢結果list
    List<E> list;
    try {
      // queryStack用來記錄當前有幾條同樣的查詢語句在同時執行,也就是併發
      queryStack++;
      // 未定義resultHandler時,先嚐試從緩存中取。
      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();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // statement級別的緩存,只緩存id相同的sql。當所有查詢語句和延遲加載的查詢語句均執行完畢後,可清空cache。這樣可節約內存
        clearLocalCache();
      }
    }
    return list;
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

調度器的query方法先從MappedStatement中獲取BoundSql,它包含了sql語句和入參對象等變量,再構造緩存的key,即cacheKey。然後先嚐試從緩存中取,緩存未命中則直接從數據庫中查詢。最後處理延遲加載,直接從緩存中取出查詢數據即可。下面我們着重分析直接從數據庫中查詢的過程,也即queryFromDatabase()方法。

// 直接從數據庫中查詢
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  // 先利用佔位符將本次查詢設置到本地cache中,個人理解是防止後面延遲加載時cache爲空
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    // 真正的數據庫查詢,後面詳細分析
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    // 查到了結果後,將前面的佔位符的cache刪掉
    localCache.removeObject(key);
  }

  // 將查詢結果放到本地cache中緩存起來
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

doQuery()進行真正的數據庫查詢,它由SimpleExecutor等具體類來實現。我們以SimpleExecutor爲例分析。

// 數據庫查詢
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,用來執行sql語句。SimpleExecutor創建的是RoutingStatementHandler。
    // 它的是一個門面類,幾乎所有方法都是通過代理來實現。代理則由配置XML settings節點的statementType區分。故僅僅是一個分發和路由。後面詳細分析
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

    // 構造Statement,後面詳細分析
    stmt = prepareStatement(handler, ms.getStatementLog());

    // 通過語句執行器的query方法進行查詢, 查詢結果通過resultHandler處理後返回。後面詳細分析
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

doQuery流程爲

  1. 先創建StatementHandler語句處理器。前面講過StatementHandler是mybatis四大組件之一,負責sql語句的執行。根據XML配置文件的settings節點的statementType子元素,來創建不同的實現類,如SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler。他們的基類統一爲BaseStatementHandler,外觀類爲RoutingStatementHandler(後面詳細分析)。
  2. 創建完StatementHandler後,調用prepareStatement進行初始化,
  3. 然後調用實現類的query方法進行查詢。

2.3 StatementHandler的query(), 語句處理器進行查詢

下面我們來看StatementHandler是如何執行的,先看StatementHandler的創建過程。

2.3.1 StatementHandler的創建過程

// 創建RoutingStatementHandler,它是StatementHandler的外觀類,也是StatementHandler的一個實現類
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  // 直接構造一個RoutingStatementHandler
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  // 將statementHandler,添加爲插件的目標執行器。插件通過配置XML文件的plugins節點設置。
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

// RoutingStatementHandler的構造器,根據statementType變量來創建不同的StatementHandler實現,作爲它的代理
// RoutingStatementHandler的幾乎所有方法都是通過這些代理實現的,典型的代理模式。
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 根據statementType創建不同的StatementHandler實現類。statementType是在xml配置文件的settngs節點的statementType子元素中設置的。
    switch (ms.getStatementType()) {
      case STATEMENT:
        // 直接操作sql,不進行預編譯。此時直接進行字符串拼接構造sql String
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        // 默認的類型。預處理,需要進行預編譯。可以使用參數替換,會將#轉換爲?,再設置對應的參數的值。
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        // 執行存儲過程
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

2.3.2 StatementHandler的初始化

StatementHandler的初始化如下

// 通過事務構造sql執行語句statement,如JdbcTransaction
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;

  // 開啓數據庫連接,創建Connection對象。JdbcTransaction事務直接通過JDBC創建connection
  Connection connection = getConnection(statementLog);

  // 初始化statement並設置期相關變量,不同的StatementHandler實現不同。後面以RoutingStatementHandler爲例分析
  stmt = handler.prepare(connection, transaction.getTimeout());

  // 設置parameterHandler,對於SimpleStatementHandler來說不用處理
  handler.parameterize(stmt);
  return stmt;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

StatementHandler初始化步驟如下:

  1. 先開啓一個數據庫連接connection,
  2. 然後初始化statementHandler,
  3. 最後進行參數預處理。

先開啓數據庫連接connection,直接獲取數據源dataSource的connection,即通過數據庫本身來開啓連接。

// JdbcTransaction和ManagedTransaction都是直接調用dataSource的getConnection
protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection();
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

再進行初始化statementHandler,調用基類BaseStatementHandler的prepare方法完成

// 初始化statementHandler
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  // 典型的代理模式,不同的statementType創建不同的Statement,但這兒他們都調用到他們的基類BaseStatementHandler中的prepare方法
  return delegate.prepare(connection, transactionTimeout);
}

// BaseStatementHandler初始化statement並設置期相關變量,不同的StatementHandler實現不同。
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // 初始化statement,由具體的StatementHandler來實現。比如SimpleStatementHandler通過JDBC connection的createStatement來創建
      statement = instantiateStatement(connection);

      // 設置timeout(超時時間)和fetchSize(獲取數據庫的行數)
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

最後進行參數預處理。不同的statementHandler實現類有不同的參數預處理方式。

  1. SimpleStatementHandler不進行任何參數預處理,它的sql直接通過字符串拼接而成。
  2. PreparedStatementHandler進行預處理,會將#轉換爲?,然後設置對應的變量到sql String中。
// RoutingStatementHandler的parameterize方法,通過代理模式實現
public void parameterize(Statement statement) throws SQLException {
  // 又是代理模式,由具體的statementHandler實現類來實現
  delegate.parameterize(statement);
}

// SimpleStatementHandler不做參數預處理
public void parameterize(Statement statement) throws SQLException {
    // N/A
}

// PreparedStatementHandler進行參數預處理,通過parameterHandler實現
public void parameterize(Statement statement) throws SQLException {
   // parameterHandler可以由用戶通過插件方式實現,mybatis默認爲DefaultParameterHandler。這個方法我們不進行詳細分析了。
    parameterHandler.setParameters((PreparedStatement) statement);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

2.3.3 statementHandler的query()進行數據庫查詢

創建和初始化statementHandler後,就可以調用它的query()方法來執行語句查詢了。先看SimpleStatementHandler的query過程。

// SimpleStatementHandler的query操作過程
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  // 獲取存放在boundSql中的sql執行語句
  String sql = boundSql.getSql();

  // 通過JDBC sql的statement,直接執行sql語句。入參在statement預編譯時進行了轉換並設置到statement中了。
  statement.execute(sql);

  // resultSetHandler處理查詢結果,並返回。這一部分十分複雜,但也體現了mybatis的設計精巧之處,可以兼容很多複雜場景下的數據庫結果轉換。如數據庫列名和Java POJO屬性名不同時的映射,關聯數據庫的映射等。
  return resultSetHandler.<E>handleResultSets(statement);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

PrepareStatementHandler的query操作過程如下

// PrepareStatementHandler的query操作過程
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  // PREPARE方式下,sql statement進行了預編譯,並注入了入參。它是一個PreparedStatement類型
  PreparedStatement ps = (PreparedStatement) statement;

  // 直接調用JDBC PreparedStatement的execute方法操作數據庫。大家應該對這兒很熟悉了,JDBC的操作
  ps.execute();

  // 結果集處理,後面詳細分析
  return resultSetHandler.<E> handleResultSets(ps);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

query先從boundSql中獲取具體執行語句,然後通過JDBC的statement直接執行SQL語句。這兩步完成後,就從數據庫中查找到了結果集了。從這兒可見,mybatis最底層還是通過JDBC來操作數據庫的。

mybatis對結果集的處理十分複雜,我們下面詳細分析。

2.4 ResultSetHandler處理數據庫結果集

通過JDBC完成數據庫的操作後,我們就拿到了原始的數據庫結果了。此時要將數據庫結果集ResultSet轉換爲Java POJO。這一步通過mybatis四大組件之一的ResultSetHandler來實現。ResultSetHandler默認實現類爲DefaultResultSetHandler,用戶也可以通過插件的方式覆蓋它。插件在xml配置文件的plugins子節點下添加。下面詳細分析DefaultResultSetHandler的handleResultSets()方法。

// DefaultResultSetHandler通過handleResultSets處理數據庫結果集,處理後作爲真正的結果返回。此處的關鍵是處理resultMap映射
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<Object>();
    int resultSetCount = 0;

    // 1 從JDBC操作數據庫後的statement中取出結果集ResultSet
   ResultSetWrapper rsw = getFirstResultSet(stmt);

    // 2 獲取resultMaps, mapper.xml中設置,並在mybatis初始化階段存入mappedStatement中。
    // resultMap定義了jdbc列到Java屬性的映射關係,可以解決列名和Java屬性名不一致,關聯數據庫映射等諸多問題。
    // 它是mybatis中比較複雜的地方,同時也大大擴展了mybatis的功能
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);

    // 3 一條條處理resultSet
    while (rsw != null && resultMapCount > resultSetCount) {
      // 取出一條resultMap,即結果映射
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 進行數據庫列到Java屬性的映射,後面詳細分析
      handleResultSet(rsw, resultMap, multipleResults, null);
      // 取出下一條resultSet
      rsw = getNextResultSet(stmt);
      // 清空nestedResultObjects,即嵌套的Result結果集
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    // 4 處理嵌套的resultMap,即映射結果中的某些子屬性也需要resultMap映射時
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        // 取出父ResultMapping,用於嵌套情況
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          // 通過嵌套ResultMap的id,取出ResultMap
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);

          // 處理ResultSet,後面詳細分析
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    // 5 構造成List,將處理後的結果集返回
    return collapseSingleResultList(multipleResults);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

handleResultSets流程如下

  1. 從JDBC操作數據庫後的statement中取出結果集ResultSet
  2. 獲取resultMaps, 它們定義了數據庫結果集到Java POJO的映射關係
  3. 一條條處理resultSet,調用handleResultSet做數據庫列到Java屬性的映射
  4. 處理嵌套的resultMap,即映射結果中的某些子屬性也需要resultMap映射時
  5. 構造成List,將處理後的結果集返回

這其中的關鍵是handleResultSet()方法進行數據庫列到Java屬性的映射,也是ORM關鍵所在。我們接着分析。

// 通過resultMap映射,處理數據庫結果集resultSet
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
  try {
    if (parentMapping != null) {
      // parentMapping不爲空,表示處理的是嵌套resultMap中的子resultMap。handleRowValues後面詳細分析
      handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
    } else {
      // 非嵌套resultMap
      if (resultHandler == null) {
        // 用戶沒有自定義resultHandler時,採用DefaultResultHandler。並將最終的處理結果添加到multipleResults中
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
        multipleResults.add(defaultResultHandler.getResultList());
      } else {
        // 用戶定義了resultHandler時,採用用戶自定義的resultHandler
        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
      }
    }
  } finally {
    // issue #228 (close resultsets)
    closeResultSet(rsw.getResultSet());
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

handleResultSet針對嵌套resultMap和非嵌套resultMap做了分別處理,但都是調用的handleRowValues()方法,接着看。

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  if (resultMap.hasNestedResultMaps()) {
    // 有嵌套resultMap時
    ensureNoRowBounds();
    checkResultHandler();
    handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  } else {
    // 無嵌套resultMap時
    handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

嵌套resultMap的處理比較麻煩,這兒不分析了,我們看非嵌套的,即handleRowValuesForSimpleResultMap。

// 非嵌套ResultMap的處理方法。根據resultMap一行行處理數據庫結果集到Java屬性的映射
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
  DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();

  // mybatis的邏輯分頁規則爲跳過rowBounds中offset之前的部分,取limit行數的數據
  // skipRows方法會跳過rowBounds中offset之前的部分。
  skipRows(rsw.getResultSet(), rowBounds);

  // 一行行處理數據庫結果集,直到取出的行數等於rowBounds的limit變量(邏輯分頁),或者所有行都取完了。
  while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
    // 處理resultMap中的discriminator,使用結果值來決定使用哪個結果映射。可以將不同的數據庫結果映射成不同的Java類型。此處不詳細分析了
    ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
    // 處理這一行數據, 得到映射後的Java結果
    Object rowValue = getRowValue(rsw, discriminatedResultMap);
    // 使用resultHandler處理得到的Java結果,這纔是最終返回的Java屬性值。
    // 用戶可自定義resultHandler,否則使用DefaultResultHandler。
    // 用戶可使用ResultSetHandler插件來自定義結果處理方式,此處體現了mybatis設計精巧之處
    storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
  }
}

// 邏輯分頁rowBounds。skipRows方法會跳過rowBounds中offset之前的部分
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
    if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
      if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
        rs.absolute(rowBounds.getOffset());
      }
    } else {
      for (int i = 0; i < rowBounds.getOffset(); i++) {
        rs.next();
      }
    }
 }

// 邏輯分頁rowBounds。shouldProcessMoreRows取limit條數據庫查詢結果
private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) throws SQLException {
    return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

mybatis的邏輯分頁規則爲跳過rowBounds中offset之前的部分,取limit行數的數據。通過skipRows()和shouldProcessMoreRows()兩個方法共同完成這個功能。遍歷resultSet,通過getRowValue()方法處理一行行數據。最後調用resultHandler來處理轉換後的結果。

storeObject結果處理的代碼如下

// 利用resultHandler處理經過resultMap映射的Java結果
private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
    if (parentMapping != null) {
     // 嵌套的resultMap,也就是子resultSet結果。鏈接到父resultSet中,由父resultMap一起處理。不詳細分析了
      linkToParents(rs, parentMapping, rowValue);
    } else {
      // 不是嵌套時,直接調用resultHandler進行最後的處理,後面詳細看
      callResultHandler(resultHandler, resultContext, rowValue);
    }
}

private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
  // 構建resultContext上下文,然後利用resultHandler處理。後面以DefaultResultHandler來分析
  resultContext.nextResultObject(rowValue);
  ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}

// ResultHandler對映射後的結果做最後的處理
public void handleResult(ResultContext<? extends Object> context) {
  // DefaultResultHandler對經過resultMap映射後的Java結果不做任何處理,僅僅添加到list中,最後將list返回給selectList()等方法。
  list.add(context.getResultObject());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

3 總結

mybatis操作數據庫的流程,也是它的四大組件Executor StatementHandler ParameterHandler ResultSetHandler的執行過程。其中Executor是調度器,StatementHandler爲SQL語句執行器,ParameterHandler爲入參執行器,ResultSetHandler爲結果集映射執行器。四大組件分層合理,運行流程清晰,都有默認實現,同時用戶也可以利用plugin來覆蓋它。這些無一不體現了mybatis的靈活和設計精巧,值得我們平時設計構架時學習。

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