美文网首页
MyBatis印象阅读之NodeHandler和SqlNode解

MyBatis印象阅读之NodeHandler和SqlNode解

作者: 向光奔跑_ | 来源:发表于2019-08-06 14:50 被阅读0次

回想在上一章中我们欠下的技术债:

nodeHandler
sqlNode
sqlSource
ParameterMapping

下面我们将一个个解决,首先我们先选择nodeHandler和sqlNode这个技术点,因为他们是连在一起的。

1. NodeHandler源码分析

首先我们先要回顾下之前是在哪里欠下的债,不能糊里糊涂的。

之前在构建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());
  }

这些标签在我们使用MyBatis的过程中经常会用到,不过这里我们还是结合官网帮我们做的归类来进行分析。

第一批是 :

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

我们先看NodeHandler接口的方法:

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

有了这个底之后,我们接下来边进行分析,首先我们从if标签的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);
    }
  }

这里有个很冷门的知识,关于 // Prevent Synthetic Access的,具体可以推荐文档:
java合成方法
这里不做展开。我们关注点放在MyBatis本身。

OK,我们继续,在上面的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);
}

总结:这里的MixedSqlNode顾名思义就是混合节点,对于存储多个SqlNode,然后调用。

我们继续向下看,介绍完了MixedSqlNode,我们来看它是怎么出现的:

  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标签,转为没一个个SqlNode,涉及到递归调用,硬看的话会比较复杂难理解,我换成一种人脑比较好理解的,用实例理解:

  <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>

解析:在这里,因为是递归的,所以我们先会解析<if>,在解析<foreach>最后解析<select>,不要跟着代码顺序走,很容易让你烦躁,失去耐心,其实理解其意便可。最主要还是理解解析的流程:

  • 如果是单纯的SQL,内部没有其他任何标签了,那么我们会进入到第一个方法
  • 如果内部还有标签,会把标签解析的信息放在contents中,一层套一层,最终contents会返回我们所有的标签解析信息。

解析完这些我们在回过头来看:


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

这里大概的意思是解析if标签下的内容和if标签中的test内容,放入到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来进行验证,我不太会这部分内容,这里也不做展开。只需知道这里是用来判断的。更多的可以查看调试ExpressionEvaluatorTest:

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

这一部分关于IfSqlNode标签解析的过程很可能会把你绕晕,这里我还是推荐跟读调试源码来进行阅读。

下面是我们第二个标签:choose (when, otherwise)标签


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节点,但是我们看到在注册hander的时候,我们的when节点其实注册的是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方法是主要保证只有一个Otherwise标签。

最后封装进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;
  }
}

这里的逻辑也比较简单,大家自行阅读即可。

2.今日总结

今天我们分析了关于nodeHandler和sqlnode源码的分析和解析过程,会很绕,但是如果把握住重心调试几遍,应该还是没有问题的~~~

相关文章

网友评论

      本文标题:MyBatis印象阅读之NodeHandler和SqlNode解

      本文链接:https://www.haomeiwen.com/subject/hdfidctx.html