MyBatis印象閱讀之NodeHandler和SqlNode解析(上)

回想在上一章中我們欠下的技術(shù)債:

nodeHandler
sqlNode
sqlSource
ParameterMapping

下面我們將一個(gè)個(gè)解決,首先我們先選擇nodeHandler和sqlNode這個(gè)技術(shù)點(diǎn)猛蔽,因?yàn)樗麄兪沁B在一起的。

1. NodeHandler源碼分析

首先我們先要回顧下之前是在哪里欠下的債,不能糊里糊涂的。

之前在構(gòu)建XMLScriptBuilder的過程中的initNodeHandlerMap方法


  private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
  }

這些標(biāo)簽在我們使用MyBatis的過程中經(jīng)常會(huì)用到症革,不過這里我們還是結(jié)合官網(wǎng)幫我們做的歸類來進(jìn)行分析。

第一批是 :

if
choose (when, otherwise)
trim (where, set)
foreach

我們先看NodeHandler接口的方法:

  private interface NodeHandler {
    void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
  }

有了這個(gè)底之后鸯旁,我們接下來邊進(jìn)行分析噪矛,首先我們從if標(biāo)簽的handler入手:

  private class IfHandler implements NodeHandler {
    public IfHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      String test = nodeToHandle.getStringAttribute("test");
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      targetContents.add(ifSqlNode);
    }
  }

這里有個(gè)很冷門的知識(shí),關(guān)于 // Prevent Synthetic Access的铺罢,具體可以推薦文檔:
java合成方法
這里不做展開艇挨。我們關(guān)注點(diǎn)放在MyBatis本身。

OK畏铆,我們繼續(xù)雷袋,在上面的IfHandler中吉殃,我們又引入了MixedSqlNode類辞居,我們來看下源碼:

public class MixedSqlNode implements SqlNode {
  private final List<SqlNode> contents;

  public MixedSqlNode(List<SqlNode> contents) {
    this.contents = contents;
  }

  @Override
  public boolean apply(DynamicContext context) {
    contents.forEach(node -> node.apply(context));
    return true;
  }
}

至于SqlNode接口,我們也來回憶下:

public interface SqlNode {
  boolean apply(DynamicContext context);
}

總結(jié):這里的MixedSqlNode顧名思義就是混合節(jié)點(diǎn)蛋勺,對(duì)于存儲(chǔ)多個(gè)SqlNode,然后調(diào)用抱完。

我們繼續(xù)向下看贼陶,介紹完了MixedSqlNode,我們來看它是怎么出現(xiàn)的:

  protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<>();
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }

這里就是解析sql標(biāo)簽,轉(zhuǎn)為沒一個(gè)個(gè)SqlNode碉怔,涉及到遞歸調(diào)用烘贴,硬看的話會(huì)比較復(fù)雜難理解,我換成一種人腦比較好理解的撮胧,用實(shí)例理解:

  <select id="selectOddPostsInKeysList" resultType="org.apache.ibatis.domain.blog.Post">
    SELECT *
    FROM POST P
    WHERE ID in
    <foreach item="item" index="index" collection="keys"
             open="(" separator="," close=")">
      <if test="index % 2 != 0">
        #{item}
      </if>
    </foreach>
    ORDER BY P.ID
  </select>

解析:在這里桨踪,因?yàn)槭沁f歸的,所以我們先會(huì)解析<if>,在解析<foreach>最后解析<select>,不要跟著代碼順序走芹啥,很容易讓你煩躁锻离,失去耐心,其實(shí)理解其意便可墓怀。最主要還是理解解析的流程:

  • 如果是單純的SQL汽纠,內(nèi)部沒有其他任何標(biāo)簽了,那么我們會(huì)進(jìn)入到第一個(gè)方法
  • 如果內(nèi)部還有標(biāo)簽傀履,會(huì)把標(biāo)簽解析的信息放在contents中虱朵,一層套一層,最終contents會(huì)返回我們所有的標(biāo)簽解析信息啤呼。

解析完這些我們?cè)诨剡^頭來看:


      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      String test = nodeToHandle.getStringAttribute("test");
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      targetContents.add(ifSqlNode);

這里大概的意思是解析if標(biāo)簽下的內(nèi)容和if標(biāo)簽中的test內(nèi)容卧秘,放入到IfSqlNode中,所以又引出了IfSqlNode類官扣。我們來看下源碼:

public class IfSqlNode implements SqlNode {
  private final ExpressionEvaluator evaluator;
  private final String test;
  private final SqlNode contents;

  public IfSqlNode(SqlNode contents, String test) {
    this.test = test;
    this.contents = contents;
    this.evaluator = new ExpressionEvaluator();
  }

  @Override
  public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }

}

其中 new ExpressionEvaluator中使用了Ognl來進(jìn)行驗(yàn)證翅敌,我不太會(huì)這部分內(nèi)容,這里也不做展開惕蹄。只需知道這里是用來判斷的蚯涮。更多的可以查看調(diào)試ExpressionEvaluatorTest:

  //ExpressionEvaluatorTest
  
  @Test
  void shouldCompareStringsReturnTrue() {
    boolean value = evaluator.evaluateBoolean("username == 'cbegin'", new Author(1, "cbegin", "******", "cbegin@apache.org", "N/A", Section.NEWS));
    assertTrue(value);
  }

這一部分關(guān)于IfSqlNode標(biāo)簽解析的過程很可能會(huì)把你繞暈,這里我還是推薦跟讀調(diào)試源碼來進(jìn)行閱讀卖陵。

下面是我們第二個(gè)標(biāo)簽:choose (when, otherwise)標(biāo)簽


private class ChooseHandler implements NodeHandler {
    public ChooseHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      List<SqlNode> whenSqlNodes = new ArrayList<>();
      List<SqlNode> otherwiseSqlNodes = new ArrayList<>();
      handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
      SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
      ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
      targetContents.add(chooseSqlNode);
    }

    private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) {
      List<XNode> children = chooseSqlNode.getChildren();
      for (XNode child : children) {
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler instanceof IfHandler) {
          handler.handleNode(child, ifSqlNodes);
        } else if (handler instanceof OtherwiseHandler) {
          handler.handleNode(child, defaultSqlNodes);
        }
      }
    }

    private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
      SqlNode defaultSqlNode = null;
      if (defaultSqlNodes.size() == 1) {
        defaultSqlNode = defaultSqlNodes.get(0);
      } else if (defaultSqlNodes.size() > 1) {
        throw new BuilderException("Too many default (otherwise) elements in choose statement.");
      }
      return defaultSqlNode;
    }
  }

}

這一段的代碼比較長遭顶,在handleWhenOtherwiseNodes主要是用來解析choose下的when和otherwise節(jié)點(diǎn),但是我們看到在注冊(cè)hander的時(shí)候泪蔫,我們的when節(jié)點(diǎn)其實(shí)注冊(cè)的是IfHandler棒旗,顧所以這里判斷了IfHandler。


  private class OtherwiseHandler implements NodeHandler {
    public OtherwiseHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      targetContents.add(mixedSqlNode);
    }
  }

而getDefaultSqlNode方法是主要保證只有一個(gè)Otherwise標(biāo)簽撩荣。

最后封裝進(jìn)ChooseSqlNode:


public class ChooseSqlNode implements SqlNode {
  private final SqlNode defaultSqlNode;
  private final List<SqlNode> ifSqlNodes;

  public ChooseSqlNode(List<SqlNode> ifSqlNodes, SqlNode defaultSqlNode) {
    this.ifSqlNodes = ifSqlNodes;
    this.defaultSqlNode = defaultSqlNode;
  }

  @Override
  public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : ifSqlNodes) {
      if (sqlNode.apply(context)) {
        return true;
      }
    }
    if (defaultSqlNode != null) {
      defaultSqlNode.apply(context);
      return true;
    }
    return false;
  }
}

這里的邏輯也比較簡(jiǎn)單铣揉,大家自行閱讀即可。

2.今日總結(jié)

今天我們分析了關(guān)于nodeHandler和sqlnode源碼的分析和解析過程餐曹,會(huì)很繞逛拱,但是如果把握住重心調(diào)試幾遍,應(yīng)該還是沒有問題的~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末台猴,一起剝皮案震驚了整個(gè)濱河市朽合,隨后出現(xiàn)的幾起案子俱两,更是在濱河造成了極大的恐慌,老刑警劉巖曹步,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宪彩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡讲婚,警方通過查閱死者的電腦和手機(jī)毯焕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來磺樱,“玉大人纳猫,你說我怎么就攤上這事≈褡剑” “怎么了芜辕?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長块差。 經(jīng)常有香客問我侵续,道長,這世上最難降的妖魔是什么憨闰? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任状蜗,我火速辦了婚禮,結(jié)果婚禮上鹉动,老公的妹妹穿的比我還像新娘轧坎。我一直安慰自己,他們只是感情好泽示,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布缸血。 她就那樣靜靜地躺著,像睡著了一般械筛。 火紅的嫁衣襯著肌膚如雪捎泻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天埋哟,我揣著相機(jī)與錄音笆豁,去河邊找鬼。 笑死赤赊,一個(gè)胖子當(dāng)著我的面吹牛闯狱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播砍鸠,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼扩氢,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼耕驰!你這毒婦竟也來了爷辱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎饭弓,沒想到半個(gè)月后双饥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弟断,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年咏花,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阀趴。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昏翰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刘急,到底是詐尸還是另有隱情棚菊,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布叔汁,位于F島的核電站统求,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏据块。R本人自食惡果不足惜码邻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望另假。 院中可真熱鬧像屋,春花似錦、人聲如沸边篮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽苟耻。三九已至篇恒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凶杖,已是汗流浹背胁艰。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留智蝠,地道東北人腾么。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像杈湾,于是被迫代替她去往敵國和親解虱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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