Mybatis 源碼(四)Mybatis Excuter框架

我們?cè)谏弦徽陆榻B到咱枉,Mybatis會(huì)將所有數(shù)據(jù)庫操作轉(zhuǎn)換成iBatis編程模型,通過門面類SqlSession來操作數(shù)據(jù)庫,但是我們深入SqlSession源碼我們會(huì)發(fā)現(xiàn),SqlSession啥都沒干,它將數(shù)據(jù)庫操作都委托給你了Excuter斩萌,如圖:

SqlSession嵌套圖.png
Executor接口定義.png

Excuter框架類圖

Executor.png

BaseExecutor

在BaseExecutor定義了Executor的基本實(shí)現(xiàn)缝裤,如查詢一級(jí)緩存,事務(wù)處理等不變的部分颊郎,操作數(shù)據(jù)庫等變化部分由子類實(shí)現(xiàn)憋飞,使用了模板設(shè)計(jì)模式,下面我們來看下查詢方法的源碼:

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameter);
  CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

/**
 * 所有的查詢操作最后都是由該方法來處理的
 */
@SuppressWarnings("unchecked")
@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.");
  }
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    // 清空本地緩存
    clearLocalCache();
  }
  List<E> list;
  try {
    // 查詢層次加一
    queryStack++;
    // 查詢一級(jí)緩存
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      // 處理存儲(chǔ)過程的OUT參數(shù)
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      // 緩存未命中姆吭,查詢數(shù)據(jù)庫
      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
      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);
  }
  // 將查詢結(jié)果添加到本地緩存
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    // 如果是存儲(chǔ)過程則榛做,緩存參數(shù)
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}
BaseExecutor查詢方法流程.png

queryFromDatabase() 方法中,我們可以看到doQuery使用的是模板方法内狸,具體邏輯是由子類來實(shí)現(xiàn)的检眯,這樣做的好處是,子類只關(guān)心程序變化的部分昆淡,其他不變的部分由父類實(shí)現(xiàn)锰瘸。提高了代碼的復(fù)用性和代碼的擴(kuò)展性。

SimpleExecutor

普通的執(zhí)行器昂灵,Mybatis的默認(rèn)使用該執(zhí)行器避凝,每次新建Statement舞萄。我們還是來看下查詢方法的源碼:

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    // 獲取Mybatis配置類
    Configuration configuration = ms.getConfiguration();
    // 根據(jù)配置類獲取StatementHandler
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    // 創(chuàng)建Statement
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  // 獲取Connection連接
  Connection connection = getConnection(statementLog);
  // 根據(jù)Connection獲取Statement
  stmt = handler.prepare(connection, transaction.getTimeout());
  // 設(shè)置參數(shù)
  handler.parameterize(stmt);
  return stmt;
}

通過stmt = handler.prepare(connection, transaction.getTimeout());方法我們可以看出每次是新建Statement

ReuseExecutor

可以重用的執(zhí)行器管削,復(fù)用的是Statement倒脓,內(nèi)部以sql語句為key使用一個(gè)Map將Statement對(duì)象緩存起來,只要連接不斷開含思,那么Statement就可以重用崎弃。

因?yàn)槊恳粋€(gè)新的SqlSession都有一個(gè)新的Executor對(duì)象,所以我們緩存在ReuseExecutor上的Statement的作用域是同一個(gè)SqlSession茸俭,所以其實(shí)這個(gè)緩存用處其實(shí)并不大吊履。我們直接看下獲取Statement源碼,其他部分和SimpleExecutor查詢方法一樣调鬓。

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  BoundSql boundSql = handler.getBoundSql();
  String sql = boundSql.getSql();
  if (hasStatementFor(sql)) {
    // 獲取復(fù)用的Statement
    stmt = getStatement(sql);
    applyTransactionTimeout(stmt);
  } else {
    // 新建Statement艇炎,并緩存
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    putStatement(sql, stmt);
  }
  handler.parameterize(stmt);
  return stmt;
}

private boolean hasStatementFor(String sql) {
  try {
    // 根據(jù)sql判斷是否緩存了Statement,并判斷Connection是否關(guān)閉
    return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed();
  } catch (SQLException e) {
    return false;
  }
}

private Statement getStatement(String s) {
  return statementMap.get(s);
}

private void putStatement(String sql, Statement stmt) {
  statementMap.put(sql, stmt);
}
ReuseExecutor.png

BatchExecutor

批處理執(zhí)行器腾窝,通過封裝jdbc的 statement.addBatch(String sql) 以及 statement.executeBatch(); 來實(shí)現(xiàn)的批處理缀踪。該執(zhí)行器的事務(wù)只能是手動(dòng)提交模式。

我們平時(shí)執(zhí)行批量的處理是一般還可以使用sql拼接的方式虹脯。

執(zhí)行批量更新時(shí)建議一次不要更新太多數(shù)據(jù)驴娃,如果更新數(shù)據(jù)量比較大時(shí)可以分段執(zhí)行。

CachingExecutor

如果開啟了二級(jí)緩存那么Mybatis會(huì)使用CachingExecutor執(zhí)行器循集,CachingExecutor使用了裝飾器模式唇敞。

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
  throws SQLException {
  // 獲取二級(jí)緩存
  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) {
        // 調(diào)用被裝飾則的方法
        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        // 將數(shù)據(jù)放入緩存
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  // 沒找到緩存,直接調(diào)用被裝飾則的方法
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

通過源碼我們發(fā)現(xiàn)咒彤,整個(gè)查詢流程變成 了 L2 -> L1 -> DB疆柔。CachingExecutor的查詢流程,增加了二級(jí)緩存的查詢操作镶柱。

我們?cè)趯?shí)際使用緩存過程中一般很少使用Mybatis的二級(jí)緩存旷档,如果想做二級(jí)緩存,建議直接在service層面使用第三方緩存框架歇拆,推薦使用為監(jiān)控而生的多級(jí)緩存框架 layering-cache鞋屈,使用更方便靈活,查詢流程是 L1 -> L2 -> DB故觅。

總結(jié)

  • BaseExecutor:使用了模板方法模式厂庇,定義了Executor的基本實(shí)現(xiàn),它是一個(gè)抽象類输吏,不能直接對(duì)外提供服務(wù)宋列。
  • SimpleExecutor:普通的執(zhí)行器,Mybatis的默認(rèn)使用該執(zhí)行器评也,每次新建Statement炼杖。
  • ReuseExecutor:可以重用Statement的執(zhí)行器灭返,但是這個(gè)Statement緩存只在一次SqlSession中有效,我們平時(shí)生少有在一次SqlSession中進(jìn)行多次一樣的查詢操作坤邪,所以性能提升并不大熙含。
  • BatchExecutor:批處理執(zhí)行器
  • CachingExecutor:二級(jí)緩存執(zhí)行器,使用裝飾器模式艇纺,整個(gè)查詢流程變成 了 L2 -> L1 -> DB怎静。建議直接使用第三方緩存框架,如:為監(jiān)控而生的多級(jí)緩存框架 layering-cache黔衡。

Mybatis 源碼中文注釋

https://github.com/xiaolyuh/mybatis

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蚓聘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子盟劫,更是在濱河造成了極大的恐慌夜牡,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侣签,死亡現(xiàn)場(chǎng)離奇詭異塘装,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)影所,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門蹦肴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人猴娩,你說我怎么就攤上這事阴幌。” “怎么了卷中?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵矛双,是天一觀的道長。 經(jīng)常有香客問我仓坞,道長背零,這世上最難降的妖魔是什么腰吟? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任无埃,我火速辦了婚禮,結(jié)果婚禮上毛雇,老公的妹妹穿的比我還像新娘嫉称。我一直安慰自己,他們只是感情好灵疮,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布织阅。 她就那樣靜靜地躺著,像睡著了一般震捣。 火紅的嫁衣襯著肌膚如雪荔棉。 梳的紋絲不亂的頭發(fā)上闹炉,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音润樱,去河邊找鬼渣触。 笑死,一個(gè)胖子當(dāng)著我的面吹牛壹若,可吹牛的內(nèi)容都是我干的嗅钻。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼店展,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼养篓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赂蕴,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤柳弄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后睡腿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體语御,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年席怪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了应闯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挂捻,死狀恐怖碉纺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情刻撒,我是刑警寧澤骨田,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站声怔,受9級(jí)特大地震影響态贤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜醋火,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一悠汽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芥驳,春花似錦柿冲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春宿饱,著一層夾襖步出監(jiān)牢的瞬間熏瞄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國打工谬以, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留巴刻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓蛉签,卻偏偏與公主長得像胡陪,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子碍舍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348