Mybatis源码阅读笔记3-探究SqlNode

Mybatis 3.5.5的版本

今天来看一下在xml解析的最后一块内容SqlNode,上一篇SqlSource我们可以发现SqlSource是为了提供一个获取BoundSql的方法而存在的,那么实质上存储SQL信息和动态标签的地方是在SqlNode。

我们可以看到在DynamicSqlSource中有一个root的SqlNode,存储了动态类型的SQL信息,而其他两种SqlSource中没有SqlNode的属性,这是为什么呢?结合上一篇的内容我们可以轻易的回答,因为静态类型的SqlSource解析的时机是在启动时,所以不需要一直存储着SqlNode,而是直接存储SqlNode解析之后的内容,这一过程中也会生成SqlNode,而且两者产生SqlNode是在同样的方法中,即XMLScriptBuilder类中的parseScriptNode和parseDynamicTags方法中。

一、SqlNode的生成时机

  public SqlSource parseScriptNode() {
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

  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);
        //判断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);
  }

我们在解析select | update | delete | insert标签时会调用到parseScriptNode这个方法,而在这个方法中会调用parseDynamicTags方法。parseScriptNode在上一篇中我们已经说过了,我们现在来看一下另一个方法parseDynamicTags。

这个方法其实非常简单,通过循环的方式来解析根标签下面的所有标签和内容,并且判断是不是动态的,只要存在动态的就一定是动态的。其中for循环中的if-else if语句就是判断Node的类型,如果是CDATASection或着TextNode类型使用TextSqlNode去处理,如果是个Element(在这里就说明是个动态标签)就调用相应的handler来处理,第二种情况出现了动态标签就一定是动态的了。而第一种还不确定,因为String中可能存在“${}”,所以需要进一步判断。

解析完所以标签后会放进这个MixedSqlNode,接下来我们看一下它的结构。

二、SqlNode的分类

SqlNode本身是一个接口,只提供一个apply方法,用以解析当前SqlNode存储的SQL信息,我们看一下源码:

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

很简单是不是,其中传入的这个DynamicContext也不复杂,它的功能就是存储解析后的Sql文本,放到其中final的StringJoiner中,StringJoiner是1.8之后引入的java类,它就是一个工具类能够方便拼接字符串的,有兴趣的可以自己研究一下。在这里我们不展开,我们继续看一下SqlNode的实现又有那些:

我简单列出来四个实现类,其实还有更多,不过这四种应该可以代表全部的类型,我们逐一分析。

1.首先分析MixedSqlNode这个实现类。它实质上储存的就是一个SqlNode的List,(一)中的parseDynamicTags方法生成的就是一个MixedSqlNode,其中包含了下面的所有SqlNode。而且DynamicSqlSource中存储的rootSqlNode就是一个MixedSqlNode。我们再来看一下它的apply具体实现:

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

很简单,就是把自己存的这个List分别调用apply方法。

2.我们再来看一下parseDynamicTags方法中用到的TextSqlNode实现类。我们在parseDynamicTags方法中调用了TextSqlNode的isDynamic方法,其中就是用到了如上图(SqlNode关系图)所示的两个内部类对SQL语句进行判断是否是动态的。

我们也来看一下它的apply方法:

  public boolean apply(DynamicContext context) {
    GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
    context.appendSql(parser.parse(text));
    return true;
  }

  private GenericTokenParser createParser(TokenHandler handler) {
    return new GenericTokenParser("${", "}", handler);
  }

结合RawSqlSource中对SqlNode的解析过程部分代码如下:

  //这段代码来自于SqlSourceBuilder类
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

结合两者可知,GenericTokenParser + TokenHandler就是用来处理SQL字符串的,可以进行解析和替换等操作,具体可以通过实现一个TokenHandler来做到。之后把解析过的Sql文本加到原来的DynamicContext中。

3.在parseDynamicTags方法中,我们对TextSqlSource进行了判断,如果不是动态的,我们会把数据存在一个StaticTextSqlNode,那么这个实现类实际上就是一个最简单的类,其中直接存储着String格式的SQL的文本,apply的时候直接调用append方法就可以了。

4.最后分析的类是IfSqlNode实现类,它也可以代表所以的其他的实现类,它们都是对应于动态标签建立的。每一个这样的动态标签都会使用一个相应的handler来处理节点得到,如(一)中的代码所示,那么这些handler是在XMLScriptBuilder类创建的时候就初始化的,可以看到这篇文章的3.3中initNodeHandlerMap方法。

那么IfSqlNode的apply方法十分简单且好理解,就是通过一个ExpressionEvaluator检测test条件,如果合适,就会执行IfSqlNode中存储的contents的apply方法。

  private final ExpressionEvaluator evaluator;
  private final String test;
  private final SqlNode contents;
 public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }

总结一下,并不是所有SqlNode标签中都直接储存着SQL文本信息,例如IfSqlNode,它中直接存的就是test的信息,而其中的SQL文本和可能会嵌套的其他标签或文本通过SqlNode的嵌套来进行存储。

至此,通过前面两篇文章和这篇文章我们已经了解了Mybatis将我们的config.xml和mapper.xml解析以及储存的主要过程,接下来会对Mybatis执行的主要过程进行研究。

前两篇文章:xml文件加载过程SqlSource的内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zxzfcsu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值