帶你一步一步手撕 Mybatis 源碼加手繪流程圖——執行部分

在上篇文章中,我向你們介紹了 Mybatis 是如何構建的,總的來講構建部分就是對於配置文件的映射,而 Mybatis 中另外一個很重要的部分就是如何去經過這些配置文件封裝成的配置對象去執行用戶指定的 SQL 語句而且將結果集封裝成用戶須要的類型java

寫源碼分析也寫了好幾篇文章了,我的以爲若是你真的想去弄懂源碼原理的,必需要勤動手,光看文章是沒有用的,你須要本身去嘗試 debug,因爲我寫的一些源碼分析篇幅都較長,若是剛興趣最好收藏再看,若是以爲我寫的還不錯那麼就不要吝嗇本身的贊 (#^.^#) 數據庫

從 DefaultSqlSession 開始

在上篇文章中,咱們知道了咱們須要建立一個 Sql會話 來執行 CRUD 操做,在 Mybatis 中就是指 SqlSession 接口,其中有一個默認的實現類 DefaultSqlSession ,通常咱們使用的就是它。緩存

咱們能夠看一看 DefaultSqlSession 中的一些方法定義,其中主要就是一些 CRUD 操做。安全

public class DefaultSqlSession implements SqlSession {

  // 配置對象
  private Configuration configuration;
  // 執行器
  private Executor executor;
  // 是否自動提交
  private boolean autoCommit;
  private boolean dirty;
  
  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {}

  public DefaultSqlSession(Configuration configuration, Executor executor) {}

  @Override
  public <T> T selectOne(String statement) {}

  //核心selectOne
  @Override
  public <T> T selectOne(String statement, Object parameter) {}

  @Override
  public <K, V> Map<K, V> selectMap(String statement, String mapKey) {}

  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {}

  //核心selectMap
  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {}

  @Override
  public <E> List<E> selectList(String statement) {}

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {}

  //核心selectList
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {}

  @Override
  public void select(String statement, Object parameter, ResultHandler handler) {}

  @Override
  public void select(String statement, ResultHandler handler) {}

  //核心select,帶有ResultHandler,和selectList代碼差很少的,區別就一個ResultHandler
  @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {}

  @Override
  public int insert(String statement) {}

  @Override
  public int insert(String statement, Object parameter) {}

  @Override
  public int update(String statement) {}

  //核心update
  @Override
  public int update(String statement, Object parameter) {}

  @Override
  public int delete(String statement) {}

  @Override
  public int delete(String statement, Object parameter) {}

  @Override
  public void commit() {}

  //核心commit
  @Override
  public void commit(boolean force) {}

  @Override
  public void rollback() {}

  //核心rollback
  @Override
  public void rollback(boolean force) {}

  //核心flushStatements
  @Override
  public List<BatchResult> flushStatements() {}

  // 核心close
  @Override
  public void close() {}

  @Override
  public Configuration getConfiguration() {return configuration;}

  //最後會去調用MapperRegistry.getMapper
  @Override
  public <T> T getMapper(Class<T> type) {}

  @Override
  public Connection getConnection() {}

  //核心clearCache
  @Override
  public void clearCache() {}

  //檢查是否須要強制commit或rollback
  private boolean isCommitOrRollbackRequired(boolean force) {}

  //把參數包裝成Collection
  private Object wrapCollection(final Object object) {}

  //嚴格的Map,若是找不到對應的key,直接拋BindingException例外,而不是返回null
  public static class StrictMap<V> extends HashMap<String, V> {}

}
複製代碼

其實主要的就是一些 CRUD 操做,今天咱們就來分析一下——Mybatisselect 這樣的操做是怎麼執行的bash

Mybatis 執行 select操做的入口

上面的 DefaultSqlSession 源碼我只是貼了一些方法的定義,若是你具體去看源碼你會發現不少 select 方法中都會調用相同的 selectList 方法(包括 selectOne 其實也是調用的 selectList 只不過它只取了第一個元素),該方法具體源碼以下。session

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //根據statement id找到對應的MappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      //轉而用執行器來查詢結果,注意這裏傳入的ResultHandler是null
      // rowBounds 不用管 是用來進行邏輯分頁操做的
      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();
    }
}
複製代碼

處理流程很簡單分爲兩步。mybatis

  1. 根據 statementIdConfiguration 中獲取對應的 MappedStatement 對象。
  2. 傳入獲取到的 MappedStatement 而後傳入執行器 Executor 的查詢 query 方法並調用返回結果。其中還作了對參數 parameter 的包裝操做等等

在我作分析源碼的時候其實還有一個疑問,就是 這個 Executor 是哪一個具體的 Executor? 由於 Executor 只是一個接口,而且在 DefaultSqlSession 中只是定義了這麼一個字段並無進行實例化,那麼這個 Executor 究竟是接口中的哪個實現類呢?app

這個問題的緣由還須要在咱們初始化過程中去尋找。從上一篇文章中咱們知道,在 SqlSession 的構建過程當中,Mybatis 使用了 工廠模式構建者模式,最終構建的 DefaultSqlSession 是經過 DefaultSqlSessionFactory 建立的,而 DefaultSqlSessionFactory 又是經過 DefaultSqlSessionFactoryBuilder 構建的。框架

// 建立 DefaultSqlSessionFactory 
// 這是在 DefaultSqlSessionFactoryBuilder 中的方法
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
複製代碼

上面是經過 構建者模式 建立工廠,獲得工廠以後咱們還須要經過 工廠模式 建立 DefaultSqlSessionide

// 其中有不少 openSession 建立方法 最終仍是會
// 調用openSessionFromDataSource
@Override
public SqlSession openSession() {
  // 這裏傳入了一個 defaultExecutorType 
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
// 對應着上面 返回的是 DefaultExecutorType
public ExecutorType getDefaultExecutorType() {
  return defaultExecutorType;
}

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);
      //生成一個執行器(事務包含在執行器裏)
      // 這裏你會發現執行器仍是在 Configuration 中建立的
      // 咱們知道上面傳入的 type 是 default類型的
      final Executor executor = configuration.newExecutor(tx, execType);
      //而後產生一個DefaultSqlSession
      // 這裏傳入了執行器
      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();
    }
}
複製代碼

上面就是 DefaultSqlSession 的構建流程了,你會發現咱們剛剛須要找的 Executor 就是在這裏實例化了,並且最終是在 Configuration 中建立的。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    // 這句再作一下保護,囧,防止粗枝大葉的人將defaultExecutorType設成null
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // 而後就是簡單的3個分支,產生3種執行器BatchExecutor/ReuseExecutor/SimpleExecutor
    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);
    }
    // 若是要求緩存,生成另外一種CachingExecutor(默認就是有緩存),裝飾者模式,因此默認都是返回CachingExecutor
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //此處調用插件,經過插件能夠改變Executor行爲
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}
複製代碼

經過上面的源代碼,咱們就能得出一個結論,若是指定了執行器類型 Executor 那麼就會建立對應類型的執行器,若是不指定那麼就是默認的 DefaultExecutor可是不論是什麼類型,若是在 Configuartion 中指定了 cacheEnabled 爲 true (即開啓緩存),那麼就會經過一個 CachingExecutor 再次包裝本來的 Executor,因此若是開啓緩存那麼返回的就是 CachingExecutor 。這裏用到了 裝飾器模式,咱們能夠看一下 執行器的 UML 圖。

固然這個時候你確定還會有一個問題,這個 cacheEnabled 又是啥時候初始化的?這裏我就長話短說,還記得咱們進行配置文件到配置對象是經過 XMLConfigBuilder 這個構造者嗎?其實這個 cachedEnabledmybatis-config.xml 文件中的 <settings> 標籤底下須要配置的,若是沒有配置那麼 Mybatis 會默認配置爲 true。下面我貼出了默認配置的源碼,其中就有 cachedEnabled 和 默認的執行器類型 ExecutorType,你能夠搜尋一下找到答案(這個方法在 XMLConfigBuilder 中)。

private void settingsElement(XNode context) throws Exception {
    if (context != null) {
      Properties props = context.getChildrenAsProperties();
      // Check that all settings are known to the configuration class
      //檢查下是否在Configuration類裏都有相應的setter方法(沒有拼寫錯誤)
      MetaClass metaConfig = MetaClass.forClass(Configuration.class);
      for (Object key : props.keySet()) {
        if (!metaConfig.hasSetter(String.valueOf(key))) {
          throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
        }
      }
      
      //下面很是簡單,一個個設置屬性
      //如何自動映射列到字段/ 屬性
      configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
      //緩存
      configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
      //proxyFactory (CGLIB | JAVASSIST)
      //延遲加載的核心技術就是用代理模式,CGLIB/JAVASSIST二者選一
      configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
      //延遲加載
      configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
      //延遲加載時,每種屬性是否還要按需加載
      configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
      //允不容許多種結果集從一個單獨 的語句中返回
      configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
      //使用列標籤代替列名
      configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
      //容許 JDBC 支持生成的鍵
      configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
      //配置默認的執行器
      configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
      //超時時間
      configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
      //是否將DB字段自動映射到駝峯式Java屬性(A_COLUMN-->aColumn)
      configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
      //嵌套語句上使用RowBounds
      configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
      //默認用session級別的緩存
      configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
      //爲null值設置jdbctype
      configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
      //Object的哪些方法將觸發延遲加載
      configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
      //使用安全的ResultHandler
      configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
      //動態SQL生成語言所使用的腳本語言
      configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
      //當結果集中含有Null值時是否執行映射對象的setter或者Map對象的put方法。此設置對於原始類型如int,boolean等無效。 
      configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
      //logger名字的前綴
      configuration.setLogPrefix(props.getProperty("logPrefix"));
      //顯式定義用什麼log框架,不定義則用默認的自動發現jar包機制
      configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
      //配置工廠
      configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
    }
}
複製代碼

咱們在這裏小結一下,在執行 SqlSession 中的一些 select 操做的時候都會調用到一個 selectList 方法,這個方法會從 SqlSession 中的 Configuration 配置對象中獲取用戶想要的 MappedStatement(用戶本身指定 MappedStatementId ),而後將這個獲取到的 MappedStatement 對象傳入 SqlSession 中的字段 Executor 執行器的查詢方法中獲取結果集幷包裝結果集返回列表。在 Executor 初始化這條支線,使用了裝飾者模式,構建的是 CachingExecutor 而且裏面還封裝了指定的 Executor

Mybatis 是如何一步一步執行查詢方法的?

獲取結果集

咱們上面知道了在 Executor 中使用到了裝飾者模式,也就是說咱們上面的主線會先執行 CachingExecutor 中的 query 方法,這裏面其實還會調用到裏面它包裝的真正的 Executor,也就是說外面封裝的一層 CachingExecutor 只是爲了作一些緩存操做的,而真正執行的仍是本來的執行器

咱們來看一下在 CachingExecutor 進行的操做

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
    //query時傳入一個cachekey參數
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    Cache cache = ms.getCache();
    //默認狀況下是沒有開啓緩存的(二級緩存).要開啓二級緩存,你須要在你的 SQL 映射文件中添加一行: <cache/>
    //簡單的說,就是先查CacheKey,查不到再委託給實際的執行器去查
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    // 最終仍是調用的裏面封裝的委託對象的查詢方法
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
複製代碼

源碼也驗證了我上面的說法,CachingExecutor 只是作一些緩存處理的。咱們繼續看看真正的執行方法。通常來講咱們調用的是 SimpleExecutor(你能夠去看剛剛的 setting 默認配置就是 SimpleExecutor),其中 SimpleExecutor 等執行器都繼承了 BaseExecutor ,因此調用的是 BaseExecutor 中的 query 方法。爲了方便你理解我再次把 UML 圖放在這裏。

@Override
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.");
    }
    //先清局部緩存,再查詢.但僅查詢堆棧爲0,才清。爲了處理遞歸調用
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      //加一,這樣遞歸調用到上面的時候就不會再清局部緩存了
      queryStack++;
      //先根據cachekey從localCache去查
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        //若查到localCache緩存,處理localOutputParameterCache
        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) {
        // issue #482
    	//若是是STATEMENT,清本地緩存
        clearLocalCache();
      }
    }
    return list;
}

//從數據庫查
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);
    //若是是存儲過程,OUT參數也加入緩存
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

複製代碼

這兩個方法仍是作了一些基礎的處理,最終真正幹活的仍是在 doQuery 方法中。而這個方法是須要子類去實現的,咱們此次就得深刻到真正的子類 SimpleExecutor 中去了。

@Override
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
      // 這裏看到ResultHandler傳入了
      // 建立的實際上是 RoutingStatementHandler 裏面也用到了 裝飾者模式
      // 你能夠本身去看這個源碼
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 準備語句 這個裏面其實就是 原生 JDBC 代碼 你建立 Statement 的流程
      stmt = prepareStatement(handler, ms.getStatementLog());
      // StatementHandler.query
      // 真正的主線在這裏
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
}
複製代碼

這裏的主線是調用到了 StatementHandlerquery 方法,由於 StatementHandler 是一個接口,因此真正調用到的是生成的 RoutingStatementHandlerquery 方法。

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 這裏仍是裝飾者模式
    return delegate.<E>query(statement, resultHandler);
}
複製代碼

你會發現這裏面又用到了 裝飾者模式(可是這裏沒作什麼處理,而是直接調用了),最終調用的其實仍是 PreparedStatementHandler 中的 query 方法。

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 強制轉換
    PreparedStatement ps = (PreparedStatement) statement;
    // 這裏就是 JDBC 裏面的執行方法 
    ps.execute();
    // 這裏已經能獲取結果集 ResultSet 了 可是須要作 結果集的類型處理
    // 最終返回的是列表
    return resultSetHandler.<E> handleResultSets(ps);
}
複製代碼

接下來調用的是 DefaultResultSetHandler 中的處理執行結果的方法。

@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
	ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

	// <select>標籤的resultMap屬性,能夠指定多個值,多個值之間用逗號(,)分割
	// 這是上篇文章中咱們提到過得
	final List<Object> multipleResults = new ArrayList<>();

	int resultSetCount = 0;
	// 這裏是獲取第一個結果集,將傳統JDBC的ResultSet包裝成一個包含結果列元信息的ResultSetWrapper對象
	// 這裏獲取告終果集而且包裝成相應的包裝類
	ResultSetWrapper rsw = getFirstResultSet(stmt);

	// 這裏是獲取全部要映射的ResultMap(按照逗號分割出來的)
	List<ResultMap> resultMaps = mappedStatement.getResultMaps();
	// 要映射的ResultMap的數量
	int resultMapCount = resultMaps.size();
	validateResultMapsCount(rsw, resultMapCount);
	// 循環處理每一個ResultMap,通常只有一個
	while (rsw != null && resultMapCount > resultSetCount) {
		// 獲得結果映射信息
		ResultMap resultMap = resultMaps.get(resultSetCount);
		// 處理結果集
		// 從rsw結果集參數中獲取查詢結果,再根據resultMap映射信息,將查詢結果映射到multipleResults中
		// 這裏是主線 處理結果集的 查詢結果會變成列表存入 multipleResults 中去
		handleResultSet(rsw, resultMap, multipleResults, null);

		rsw = getNextResultSet(stmt);
		cleanUpAfterHandlingResultSet();
		resultSetCount++;
	}

	// 對應<select>標籤的resultSets屬性,通常不使用 不用管
	String[] resultSets = mappedStatement.getResultSets();
	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);
}
複製代碼

如今咱們就能夠畫出一個簡單的獲取結果集並返回的流程圖了。

處理結果集

在上文中咱們得知在 getFirstResultSet 方法中經過 JDBC 原生代碼獲取了 ResultSet(結果集) 而且封裝成包裝類 ResultSetWrapper,由於咱們返回給 dao 層的確定不能是原生的 ResultSet ,因此咱們須要進一步處理結果集。

接下來仍是在 DefaultResultSetHandler 中,只不過咱們的主要任務變成了如何去處理結果集?

//處理結果集
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          //若是沒有resultHandler
          //新建DefaultResultHandler
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          //調用本身的handleRowValues
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          //獲得記錄的list
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          //若是有resultHandler
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      //最後別忘了關閉結果集
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
}
複製代碼

上面的代碼邏輯總結來講就是:判斷 resultHandler 是否爲空,若是爲空則生成一個默認的 DefaultResultHandler 否則則使用本來的,最終都會去調用 handleRowValues 方法進行進一步的處理

固然咱們最終應該去處理 multipleResults 這個列表變量,而在 Mybatis 中是在 ResultHandler 中持有了一個列表字段,最終是將數據賦值到 ResultHandler 中的列表字段裏面,而後將這個列表字段加入 multipleResults 中去的。

從上面分析可知,最終都是調用的 handleRowValues 因此進行結果集的數據處理的主線還在這裏。

private void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    //是否有內嵌 不用管先
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      // 繼續處理
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
}  
複製代碼

由於不涉及到其餘複雜的步驟咱們這裏是直接分析通常流程,這裏會調用 handleRowValuesForSimpleResultMap 方法繼續進行數據處理。

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
	DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
	// 獲取結果集信息
	ResultSet resultSet = rsw.getResultSet();
	// 使用rowBounds的分頁信息,進行邏輯分頁(也就是在內存中分頁)
	// 分頁先不用管
	skipRows(resultSet, rowBounds);
	while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
		// 經過<resultMap>標籤的子標籤<discriminator>對結果映射進行鑑別
		ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
		// 將查詢結果封裝到POJO中
		// rsw 是結果集的包裝類,裏面封裝了返回結果的信息
		// 經過結果集包裝類建立 POJO
		Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
		// 處理對象嵌套的映射關係
		storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
	}
}
複製代碼

上面的方法作的事情很簡單,就是經過結果集包裝類和一開始定義的類字段(好比這個 POJO 是 Admin類其中有 account 和 password 等字段) 封裝成一個 Object 實體類。而後將這個實體類存入某個區域裏。

咱們先無論 getRowValue 方法是如何去處理的,咱們先思考一下,此時獲取到的 Object 對象是如何載入到 multipleResults 中的,或者說是如何加入到 resultHandler 中的列表字段裏的(由於這裏最終也會加入 multipleResults )

想必你也看出來了,答案確定在 storeObject 中(由於其中 resultHandler 做爲了參數傳入了進去),這裏我就作簡單分析

private void storeObject(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
    if (parentMapping != null) {
      linkToParents(rs, parentMapping, rowValue);
    } else {
      // 通常處理這裏
      callResultHandler(resultHandler, resultContext, rowValue);
    }
}
  
private void callResultHandler(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue) {
    resultContext.nextResultObject(rowValue);
    // resultContext 封裝了一些上下文信息 這裏包括封裝好的 Object
    // 這裏調用的是 resultHandler 對象呀!!!
    resultHandler.handleResult(resultContext);
}

// 這裏是在 DefaultResultHandler 中進行的 因此
// 是給裏面的列表字段進行添加 
@Override
public void handleResult(ResultContext context) {
    // 處理很簡單,就是把記錄加入List
    list.add(context.getResultObject());
}
複製代碼

好了分析完以後咱們就能夠大膽地分析如何包裝結果 POJO 對象的了,咱們來進入 getRowValue 方法中。

//核心,取得一行的值
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    //實例化ResultLoaderMap(延遲加載器)
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    //調用本身的createResultObject,內部就是new一個對象(若是是簡單類型,new完也把值賦進去)
    Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null);
    if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
      //通常不是簡單類型不會有typehandler,這個if會進來
      // 從新有作了封裝
      // 這步以後 metaObject 就持有了 resultObject
      // 這個得記住
      final MetaObject metaObject = configuration.newMetaObject(resultObject);
      boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty();
      if (shouldApplyAutomaticMappings(resultMap, false)) {        
        // 自動映射咯 這裏離是賦值操做
        // 也就是說此時咱們的 resultHandler 其中的值都是 null 或默認值
        // 這裏把每一個列的值都賦到相應的字段裏去了
    	foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
      }
      // 執行完這個方法 resultObject 中的字段纔有值
      // 你會發現這裏傳入的實際上是 metaObject
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      resultObject = foundValues ? resultObject : null;
      return resultObject;
    }
    return resultObject;
}
複製代碼

上面咱們進行了一個操做就是:使用 resultObject 和其餘一些東西構造了 metaObject 實例,若是翻看源碼你會發現這個 metaObject 裏面是持有了 resultObject ,這裏由於篇幅有限,我不作過多解釋,感興趣能夠本身去查看源碼。整個方法就是經過原先定義好的配置信息建立一個 POJO 對象的空殼,而後經過結果集中的數據給空殼賦值。

咱們來具體看一下 Mybatis 是如何給空殼賦值的。

private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    // 獲取字段名稱列表
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    // 循環賦值
    for (ResultMapping propertyMapping : propertyMappings) {
      final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      if (propertyMapping.isCompositeResult() 
          || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) 
          || propertyMapping.getResultSet() != null) {
        // 重要 從結果集包裝類裏面獲取每一個字段的值
        // 這裏面還涉及到一個很重要的類 TypeHandler
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        // issue #541 make property optional
        final String property = propertyMapping.getProperty();
        // issue #377, call setter on nulls
        if (value != NO_VALUE && property != null && (value != null || configuration.isCallSettersOnNulls())) {
          if (value != null || !metaObject.getSetterType(property).isPrimitive()) {
            // 在這裏賦值了,其實這裏面是給 metaObject 持有的
            // resultObject中的字段賦值
            metaObject.setValue(property, value);
          }
          foundValues = true;
        }
      }
    }
    return foundValues;
}
複製代碼

上面整個流程就是先獲取 POJO類 中字段的一些名稱,而後經過結果集包裝類 ResultSetWarpper 獲取結果集中對應字段的值而且賦值給 metaObject 中持有的 resultObject 「空殼」對象中的相應字段,這麼一來 resultObject 就不是空殼了。在上面我標註了一個比較重要的地方就是這段代碼。

Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
複製代碼

你有沒有想過,這其中是如何將字段本來的值包裝成一個 Object 對象的?

咱們進來找一下答案。

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    if (propertyMapping.getNestedQueryId() != null) {
      return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    } else if (propertyMapping.getResultSet() != null) {
      addPendingChildRelation(rs, metaResultObject, propertyMapping);
      return NO_VALUE;
    } else if (propertyMapping.getNestedResultMapId() != null) {
      // the user added a column attribute to a nested result map, ignore it
      return NO_VALUE;
    } else {
      // 前面的一些能夠不用看 這裏咱們涉及到了一個 TypeHandler 
      // 其中獲取到了 ResultMapping 本來的 TypeHandler
      // 而後經過 TypeHandler 這個類型處理器去處理值
      final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
      final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      return typeHandler.getResult(rs, column);
    }
}
複製代碼

也就是說最終一些對象的類型轉換是經過 TypeHandler 來實現的,這是 Mybatis 中作類型轉換的主要工具類,在上一篇文章中我也提到過。

這樣,整個 Mybatis 的基本的執行流程咱們就分析完畢了,我在網上找到一張挺好的圖,你能夠參考着我上面的分析去理解(不過我仍是建議你 debug 去看一下源碼),圖有點不清晰,目前沒找到清晰版本的。

總結

總結來講呢,整個 Mybatis 的查詢流程就是 封裝了 JDBC 原生代碼,並作了 JDBC 沒有爲咱們作的 結果集的類型轉換

相關文章
相關標籤/搜索