MyBatis 源碼解析(二):SqlSession 執(zhí)行流程

簡介

上一篇文章(MyBatis 源碼解析(一):初始化和動態(tài)代理)分析了 MyBatis 解析配置文件以及 Mapper 動態(tài)代理相關(guān)的源碼,這一篇接著上一篇探究 SqlSession 的執(zhí)行流程,另外了解一下 MyBatis 中的緩存。

openSession

MyBatis 在解析完配置文件后生成了一個 DefaultSqlSessionFactory 對象受扳,后續(xù)執(zhí)行 SQL 請求的時候都是調(diào)用其 openSession 方法獲得 SqlSessison,相當(dāng)于一個 SQL 會話兔跌。 SqlSession 提供了操作數(shù)據(jù)庫的一些方法勘高,如 selectupdate 等坟桅。

先看一下 DefaultSqlSessionFactoryopenSession 的代碼:

  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 從 configuration 取出配置
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 每個 SqlSession 都有一個單獨的 Executor 對象
      final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
      // 返回 DefaultSqlSession 對象
      return new DefaultSqlSession(configuration, executor);
    } 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();
    }
  }

主要代碼在 openSessionFromDataSource华望,首先是從 Configuration 中取出相關(guān)的配置,生成 Transaction仅乓,接著又創(chuàng)建了一個 Executor赖舟,最后返回了 DefaultSqlSession 對象。

這里的 Executor 是什么呢夸楣?它其實是一個執(zhí)行器宾抓,SqlSession 的操作會交給 Executor 去執(zhí)行子漩。MyBatis 的 Executor 常用的有以下幾種:

  • SimpleExecutor: 默認(rèn)的 Executor,每個 SQL 執(zhí)行時都會創(chuàng)建新的 Statement
  • ResuseExecutor: 相同的 SQL 會復(fù)用 Statement
  • BatchExecutor: 用于批處理的 Executor
  • CachingExecutor: 可緩存數(shù)據(jù)的 Executor石洗,用代理模式包裝了其它類型的 Executor

了解了 Executor 的類型后幢泼,看一下 configuration.newExecutor(tx, execType, autoCommit) 的代碼:

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // 默認(rèn)是 SimpleExecutor
    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);
    }
    // 默認(rèn)啟動緩存
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

MyBatis 默認(rèn)啟用一級緩存,即同一個 SqlSession 會共用同一個緩存讲衫,上面代碼最終返回的是 CachingExecutor缕棵。

getMapper

在創(chuàng)建了 SqlSession 之后,下一步是生成 Mapper 接口的代理類涉兽,代碼如下:

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

可以看出是從 configuration 中取得 Mapper招驴,最終調(diào)用了 MapperProxyFactorynewInstanceMapperProxyFactory 在上一篇文章已經(jīng)分析過枷畏,它是為了給 Mapper 接口生成代理類别厘,其中關(guān)鍵的攔截邏輯在 MapperProxy 中,下面是其 invoke 方法:

  @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);
    }
    // 從緩存中獲取 MapperMethod 然后調(diào)用
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

MapperProxy 中調(diào)用了 MapperMethodexecute矿辽,下面是部分代碼:

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;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
      ...
}

可以看出丹允,最終調(diào)用了 SqlSession 的對應(yīng)方法,也就是 DefaultSqlSession 中的方法袋倔。

select

先看一下 DefaultSqlSessionselect 的代碼:

  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

select 中調(diào)用了 executorquery雕蔽,上面提到,默認(rèn)的 ExecutorCachingExecutor宾娜,看其中的代碼:

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 獲取緩存的key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 獲取緩存
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, 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;
      }
    }
    // 調(diào)用代理對象的緩存
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

首先檢查緩存中是否有數(shù)據(jù)批狐,如果沒有再調(diào)用代理對象的 query,默認(rèn)是 SimpleExecutor前塔。Executor 是一個接口嚣艇,下面有個實現(xiàn)類是 BaseExecutor,其中實現(xiàn)了其它 Executor 通用的一些邏輯华弓,包括 doQuery 以及 doUpdate 等食零,其中封裝了 JDBC 的相關(guān)操作。

update

update 的執(zhí)行與 select 類似寂屏, 都是從 CachingExecutor 開始贰谣,看代碼:

  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    // 檢查是否需要刷新緩存
    flushCacheIfRequired(ms);
    // 調(diào)用代理類的 update
    return delegate.update(ms, parameterObject);
  }

update 會使得緩存的失效,所以第一步是檢查是否需要刷新緩存迁霎,接下來再交給代理類去執(zhí)行真正的數(shù)據(jù)庫更新操作吱抚。

總結(jié)

本文主要分析了 SqlSession 的執(zhí)行流程,結(jié)合上一篇文章基本了解了 MyBatis 的運行原理考廉。對于 MyBatis 的源碼秘豹,還有很多地方?jīng)]有深入,例如SQL 解析時參數(shù)的處理昌粤、一級緩存與二級緩存的處理邏輯等既绕,不過在熟悉 MyBatis 的整體框架之后啄刹,這些細(xì)節(jié)可以在需要用到的時候繼續(xù)學(xué)習(xí)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末岸更,一起剝皮案震驚了整個濱河市鸵膏,隨后出現(xiàn)的幾起案子膊升,更是在濱河造成了極大的恐慌怎炊,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件廓译,死亡現(xiàn)場離奇詭異行冰,居然都是意外死亡重贺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來威彰,“玉大人,你說我怎么就攤上這事克锣「视校” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵管怠,是天一觀的道長淆衷。 經(jīng)常有香客問我,道長渤弛,這世上最難降的妖魔是什么祝拯? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮她肯,結(jié)果婚禮上佳头,老公的妹妹穿的比我還像新娘。我一直安慰自己晴氨,他們只是感情好康嘉,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著籽前,像睡著了一般亭珍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上聚假,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天块蚌,我揣著相機(jī)與錄音,去河邊找鬼膘格。 笑死峭范,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瘪贱。 我是一名探鬼主播纱控,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼辆毡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了甜害?” 一聲冷哼從身側(cè)響起舶掖,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尔店,沒想到半個月后眨攘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡嚣州,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年鲫售,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片该肴。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡情竹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出匀哄,到底是詐尸還是另有隱情秦效,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布涎嚼,位于F島的核電站阱州,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铸抑。R本人自食惡果不足惜贡耽,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鹊汛。 院中可真熱鬧蒲赂,春花似錦、人聲如沸刁憋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽至耻。三九已至若皱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尘颓,已是汗流浹背走触。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留疤苹,地道東北人互广。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親惫皱。 傳聞我的和親對象是個殘疾皇子像樊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345