原生Mybatis源碼簡析(下)

在上一篇文章中(原生Mybatis源碼簡析(上))巴帮,我們介紹了原生Mybatis的初始化,以及Mapper接口的運行原理侨赡。在介紹到Executor類具體執(zhí)行SQL時摇庙,就沒有繼續(xù)下去了,現(xiàn)在我們繼續(xù)將剩下的流程梳理一下絮姆。

1醉冤、概述

在具體介紹代碼流程之前秩霍,我們得先介紹下如下四個比較重要的類,他在接下來的代碼分析中占有重要的地位蚁阳。

  1. Executor:Executor創(chuàng)建StatementHandler對象铃绒;
  2. StatementHandler: 設置sql語句,預編譯,設置參數(shù)等相關工作,以及執(zhí)行增刪改查方法;
  3. ParameterHandler: 設置預編譯參數(shù);
  4. ResultHandler: 處理查詢結果集;
    其中在設置參數(shù)和處理查詢結果時,都是依賴TypeHandler,進行數(shù)據(jù)庫類型和javaBean類型的映射

2、流程介紹

從上篇文章中螺捐,我們知道颠悬,在獲取SqlSession的同時,其實已經(jīng)生成了Executor對象了归粉,并且是作為屬性設置給了SqlSession對象椿疗。

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);
      final Executor executor = configuration.newExecutor(tx, execType);
      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();
    }
  }

我們看下Executor對象的實例化過程

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    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);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
   // 插件相關的代碼,先記住這里糠悼,后面分析Mybatis插件機制時届榄,再重點分析
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

這里executorType默認是simple,而Mybatis的二級緩存機制一般很少啟用(一般都是自己在業(yè)務層通過redis等實現(xiàn)緩存機制)倔喂,所以這里默認是返回SimpleExecutor類的實例铝条。
現(xiàn)在再回到上篇文章最后的代碼部分,DefaultSqlSession的selectList方法中席噩,調(diào)用了executor的query方法班缰。該方法會調(diào)用到BaseExecutor的query方法,不考慮一級緩存悼枢,最后會來到SimpleExecutor的doQuery方法


流程圖
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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

在該方法中埠忘,具體SQL語句的執(zhí)行會有SimpleExecutor對象委托給StatementHandler對象處理。我們先看下該對象的實例化過程

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 這里是第二次出現(xiàn)關于插件的代碼馒索,后續(xù)會分析
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

RoutingStatementHandler對象會也會根據(jù)statementType屬性實例化對應的StatementHandler對象

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        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());
    }

  }

MappedStatement的StatementType屬性默認是PREPARED(可以在MappedStatement實例化方法中看到)莹妒,這里自然是實例化PreparedStatementHandler對象

  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

這里可以看出,在初始化StatementHandler的時候绰上,就會同時實例化parameterHandler和resultSetHandler旨怠,并設置為StatementHandler的屬性。

  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 這里是第三次出現(xiàn)關于插件的代碼蜈块,后續(xù)會分析
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    // 這里是第四次出現(xiàn)關于插件的代碼鉴腻,后續(xù)會分析
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

我們再回到SimpleExecutor的doQuery方法中,先調(diào)用了prepareStatement方法百揭,完成SQL參數(shù)化配置

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

然后調(diào)用了StatementHandler的query方法

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }

之后就是jdbc的套路了爽哎,就不再繼續(xù)分析了。下面再看下Mybatis的插件機制

3器一、插件機制

在上一篇文章中课锌,我們知道在初始化Configuration對象時,會初始化InterceptorChain對象盹舞,該對象中的interceptors屬性中保存了所有的插件對象产镐。在上面的代碼介紹了,我們一共發(fā)現(xiàn)了四處踢步,使用interceptorChain屬性的pluginAll方法癣亚,這也就是為什么Mybatis支持Executor、StatementHandler获印、ParamaterHandler述雾、ResultsetHandler四個對象的插件攔截機制的原因了。我們看下plugAll方法

public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

很簡單兼丰,就是循環(huán)遍歷執(zhí)行Interceptor的plugin方法玻孟,而我們在實現(xiàn)一個插件時,通過繼承Interceptor接口鳍征,實現(xiàn)plugin方法都是如下實現(xiàn)方式黍翎。

     public Object plugin(Object target) {
         return Plugin.wrap(target, this);
     }

該Plugin類時Mybatis提供的一個工具類

  public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
    }
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
      try {
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    return signatureMap;
  }

  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<>();
    while (type != null) {
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }

可見,Mybatis的插件機制是通過動態(tài)代理實現(xiàn)的艳丛,且多個插件會層層代理匣掸,代理的順序就是在xml中配置插件的順序。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末氮双,一起剝皮案震驚了整個濱河市碰酝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌戴差,老刑警劉巖送爸,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異暖释,居然都是意外死亡袭厂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門饭入,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嵌器,“玉大人,你說我怎么就攤上這事谐丢∷剑” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵乾忱,是天一觀的道長讥珍。 經(jīng)常有香客問我,道長窄瘟,這世上最難降的妖魔是什么衷佃? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮蹄葱,結果婚禮上氏义,老公的妹妹穿的比我還像新娘锄列。我一直安慰自己,他們只是感情好惯悠,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布邻邮。 她就那樣靜靜地躺著,像睡著了一般克婶。 火紅的嫁衣襯著肌膚如雪筒严。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天情萤,我揣著相機與錄音鸭蛙,去河邊找鬼。 笑死筋岛,一個胖子當著我的面吹牛娶视,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播睁宰,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼歇万,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了勋陪?” 一聲冷哼從身側響起贪磺,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诅愚,沒想到半個月后寒锚,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡违孝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年刹前,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雌桑。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡喇喉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出校坑,到底是詐尸還是另有隱情拣技,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布耍目,位于F島的核電站膏斤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏邪驮。R本人自食惡果不足惜莫辨,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沮榜,春花似錦盘榨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至振愿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弛饭,已是汗流浹背冕末。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留侣颂,地道東北人档桃。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像憔晒,于是被迫代替她去往敵國和親藻肄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 1拒担、概述 目前工作中嘹屯,直接使用mybatis原生API開發(fā)的場景很少,基本都是結合spring一起使用从撼。但對于分析...
    Hogantry閱讀 820評論 0 0
  • Mybatis相關 1.Mybatis是什么? 2.為什么選擇Mybatis? 3州弟、#{}和${}的區(qū)別是什么? ...
    夢殤_fccd閱讀 978評論 0 5
  • Mybatis相關 1.Mybatis是什么? 2.為什么選擇Mybatis? 3低零、#{}和${}的區(qū)別是什么婆翔? ...
    zhihaoZzz閱讀 1,276評論 0 2
  • 1. 簡介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL掏婶、存儲過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,454評論 0 4
  • 我是在下香山的路遇到他的啃奴。一個小男孩,約莫八九歲的樣子雄妥,眼神透著機靈最蕾,他一手拿著罐子,里面裝有幾只飛騰的蝴蝶...
    觀舟閱讀 156評論 0 0