数据库
首页 > 数据库> > 精尽MyBatis源码分析 - MyBatis初始化(三)之 SQL 初始化(上)

精尽MyBatis源码分析 - MyBatis初始化(三)之 SQL 初始化(上)

作者:互联网

该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址Mybatis-Spring 源码分析 GitHub 地址Spring-Boot-Starter 源码分析 GitHub 地址)进行阅读

MyBatis 版本:3.5.2

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

MyBatis的初始化

在MyBatis初始化过程中,大致会有以下几个步骤:

  1. 创建Configuration全局配置对象,会往TypeAliasRegistry别名注册中心添加Mybatis需要用到的相关类,并设置默认的语言驱动类为XMLLanguageDriver

  2. 加载mybatis-config.xml配置文件、Mapper接口中的注解信息和XML映射文件,解析后的配置信息会形成相应的对象并保存到Configuration全局配置对象中

  3. 构建DefaultSqlSessionFactory对象,通过它可以创建DefaultSqlSession对象,MyBatis中SqlSession的默认实现类

因为整个初始化过程涉及到的代码比较多,所以拆分成了四个模块依次对MyBatis的初始化进行分析:

由于在MyBatis的初始化过程中去解析Mapper接口与XML映射文件涉及到的篇幅比较多,XML映射文件的解析过程也比较复杂,所以才分成了后面三个模块,逐步分析,这样便于理解

初始化(三)之SQL初始化(上)

在前面的MyBatis初始化相关文档中已经大致讲完了MyBatis初始化的整个流程,其中遗漏了一部分,就是在解析<select /> <insert /> <update /> <delete />节点的过程中,是如何解析SQL语句,如何实现动态SQL语句,最终会生成一个org.apache.ibatis.mapping.SqlSource对象的,对于这烦琐且易出错的过程,我们来看看MyBatis如何实现的?

我们回顾org.apache.ibatis.builder.xml.XMLStatementBuilderparseStatementNode()解析 Statement 节点时,通过下面的方法创建对应的SqlSource对象

// 创建对应的 SqlSource 对象,保存了该节点下 SQL 相关信息
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

langDriver是从Configuration全局配置对象中获取的默认实现类,对应的也就是XMLLanguageDriver,在Configuration初始化的时候设置的

public Configuration() {
    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
	languageRegistry.register(RawLanguageDriver.class);
}

主要包路径:org.apache.ibatis.scripting、org.apache.ibatis.builder、org.apache.ibatis.mapping

主要涉及到的类:

语言驱动接口的实现类如下图所示:

LanguageDriver

LanguageDriver

org.apache.ibatis.scripting.LanguageDriver:语言驱动接口,代码如下:

public interface LanguageDriver {

	/**
	 * Creates a {@link ParameterHandler} that passes the actual parameters to the the JDBC statement.
	 * 创建 ParameterHandler 对象
	 *
	 * @param mappedStatement The mapped statement that is being executed
	 * @param parameterObject The input parameter object (can be null)
	 * @param boundSql        The resulting SQL once the dynamic language has been executed.
	 * @return 参数处理器
	 * @author Frank D. Martinez [mnesarco]
	 * @see DefaultParameterHandler
	 */
	ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);

	/**
	 * Creates an {@link SqlSource} that will hold the statement read from a mapper xml file. 
	 * It is called during startup, when the mapped statement is read from a class or an xml file.
	 * 创建 SqlSource 对象,从 Mapper XML 配置的 Statement 标签中,即 <select /> 等。
	 *
	 * @param configuration The MyBatis configuration
	 * @param script        XNode parsed from a XML file
	 * @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null.
	 * @return SQL 资源
	 */
	SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);

	/**
	 * Creates an {@link SqlSource} that will hold the statement read from an annotation. 
	 * It is called during startup, when the mapped statement is read from a class or an xml file.
	 * 创建 SqlSource 对象,从方法注解配置,即 @Select 等。
	 *
	 * @param configuration The MyBatis configuration
	 * @param script        The content of the annotation
	 * @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null.
	 * @return SQL 资源
	 */
	SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}

定义了三个方法:

  1. createParameterHandler:获取 ParameterHandler 参数处理器对象

  2. createSqlSource:创建 SqlSource 对象,解析 Mapper XML 配置的 Statement 标签中,即 <select /> <update /> <delete /> <insert />

  3. createSqlSource:创建 SqlSource 对象,从方法注解配置,即 @Select 等

XMLLanguageDriver

org.apache.ibatis.scripting.xmltags.XMLLanguageDriver:语言驱动接口的默认实现,代码如下:

public class XMLLanguageDriver implements LanguageDriver {

  @Override
  public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    // 创建 DefaultParameterHandler 对象
    return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
  }

  /**
   * 用于解析 XML 映射文件中的 SQL
   *
   * @param configuration The MyBatis configuration
   * @param script        XNode parsed from a XML file
   * @param parameterType input parameter type got from a mapper method or
   *                      specified in the parameterType xml attribute. Can be
   *                      null.
   * @return SQL 资源
   */
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    // 创建 XMLScriptBuilder 对象,执行解析
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }

  /**
   * 用于解析注解中的 SQL
   *
   * @param configuration The MyBatis configuration
   * @param script        The content of the annotation
   * @param parameterType input parameter type got from a mapper method or
   *                      specified in the parameterType xml attribute. Can be
   *                      null.
   * @return SQL 资源
   */
  @Override
  public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
    // issue #3
    // <1> 如果是 <script> 开头,表示是在注解中使用的动态 SQL
    if (script.startsWith("<script>")) {
      // <1.1> 创建 XPathParser 对象,解析出 <script /> 节点
      XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
      return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
    } else {
      // issue #127
      // <2.1> 变量替换
      script = PropertyParser.parse(script, configuration.getVariables());
      // <2.2> 创建 TextSqlNode 对象
      TextSqlNode textSqlNode = new TextSqlNode(script);
      if (textSqlNode.isDynamic()) { // <2.3.1> 如果是动态 SQL ,则创建 DynamicSqlSource 对象
        return new DynamicSqlSource(configuration, textSqlNode);
      } else { // <2.3.2> 如果非动态 SQL ,则创建 RawSqlSource 对象
        return new RawSqlSource(configuration, script, parameterType);
      }
    }
  }
}

实现了LanguageDriver接口:

  1. 创建 DefaultParameterHandler 默认参数处理器并返回

  2. 解析 XML 映射文件中的 SQL,通过创建 XMLScriptBuilder 对象,调用其 parseScriptNode() 方法解析

  3. 解析注解定义的 SQL

    1. 如果是 <script> 开头,表示是在注解中使用的动态 SQL,将其转换成 XNode 然后调用上述方法,不了解的可以看看MyBatis三种动态SQL配置方式
    2. 先将注解中定义的 SQL 中包含的变量进行转换,然后创建对应的 SqlSource 对象

RawLanguageDriver

org.apache.ibatis.scripting.defaults.RawLanguageDriver:继承了XMLLanguageDriver,在的基础上增加了是否为静态SQL语句的校验,也就是判断创建的 SqlSource 是否为 RawSqlSource 静态 SQL 资源

XMLScriptBuilder

org.apache.ibatis.scripting.xmltags.XMLScriptBuilder:继承 BaseBuilder 抽象类,负责将 SQL 脚本(XML或者注解中定义的 SQL )解析成 SqlSource 对象

构造方法

public class XMLScriptBuilder extends BaseBuilder {
    /**
	 * 当前 SQL 的 XNode 对象
	 */
	private final XNode context;
	/**
	 * 是否为动态 SQL
	 */
	private boolean isDynamic;
	/**
	 * SQL 的 Java 入参类型
	 */
	private final Class<?> parameterType;
	/**
	 * NodeNodeHandler 的映射
	 */
	private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();

	public XMLScriptBuilder(Configuration configuration, XNode context) {
		this(configuration, context, null);
	}

	public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
		super(configuration);
		this.context = context;
		this.parameterType = parameterType;
		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());
	}
}

在构造函数中会初始化 NodeHandler 处理器,分别用于处理不同的MyBatis自定义的XML标签,例如<if /> <where /> <foreach />等标签

parseScriptNode方法

parseScriptNode()方法将 SQL 脚本(XML或者注解中定义的 SQL )解析成 SqlSource 对象,代码如下:

public SqlSource parseScriptNode() {
    // 解析 XML 或者注解中定义的 SQL
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
        // 动态语句,使用了 ${} 也算
        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
}
  1. 通过调用parseDynamicTags(XNode node)方法,将解析 SQL 成 MixedSqlNode 对象,主要是将一整个 SQL 解析成一系列的 SqlNode 对象
  2. 如果是动态SQL语句,使用了MyBatis自定义的XML标签(<if />等)或者使用了${},则封装成DynamicSqlSource对象
  3. 否则就是静态SQL语句,封装成RawSqlSource对象

parseDynamicTags方法

parseDynamicTags()将 SQL 脚本(XML或者注解中定义的 SQL )解析成MixedSqlNode对象,代码如下:

protected MixedSqlNode parseDynamicTags(XNode node) {
    // <1> 创建 SqlNode 数组
    List<SqlNode> contents = new ArrayList<>();
    /*
     * <2> 遍历 SQL 节点中所有子节点
     * 这里会对该节点内的所有内容进行处理然后返回 NodeList 对象
     * 1. 文本内容会被解析成 '<#text></#text>' 节点,就算一个换行符也会解析成这个
     * 2. <![CDATA[ content ]]> 会被解析成 '<#cdata-section>content</#cdata-section>' 节点
     * 3. 其他动态<if /> <where />
     */
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
        // 当前子节点
        XNode child = node.newXNode(children.item(i));
        // <2.1> 如果类型是 Node.CDATA_SECTION_NODE 或者 Node.TEXT_NODE 时
        if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE // <![CDATA[ ]]>节点
                || child.getNode().getNodeType() == Node.TEXT_NODE) { // 纯文本
            // <2.1.1> 获得内容
            String data = child.getStringBody("");
            // <2.1.2> 创建 TextSqlNode 对象
            TextSqlNode textSqlNode = new TextSqlNode(data);
            if (textSqlNode.isDynamic()) { // <2.1.2.1> 如果是动态的 TextSqlNode 对象,也就是使用了 '${}'
                // 添加到 contents 中
                contents.add(textSqlNode);
                // 标记为动态 SQL
                isDynamic = true;
            } else { // <2.1.2.2> 如果是非动态的 TextSqlNode 对象,没有使用 '${}'
                // <2.1.2> 创建 StaticTextSqlNode 添加到 contents 中
                contents.add(new StaticTextSqlNode(data));
            }
        } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 <2.2> 如果类型是 Node.ELEMENT_NODE
            // <2.2.1> 根据子节点的标签,获得对应的 NodeHandler 对象
            String nodeName = child.getNode().getNodeName();
            NodeHandler handler = nodeHandlerMap.get(nodeName);
            if (handler == null) { // 获得不到,说明是未知的标签,抛出 BuilderException 异常
                throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            // <2.2.2> 执行 NodeHandler 处理
            handler.handleNode(child, contents);
            // <2.2.3> 标记为动态 SQL
            isDynamic = true;
        }
    }
    // <3> 创建 MixedSqlNode 对象
    return new MixedSqlNode(contents);
}

<1> 创建 SqlNode 数组 contents,用于保存解析 SQL 后的一些列 SqlNode 对象

<2> 获取定义的 SQL 节点中所有子节点,返回一个 NodeList 对象,这个对象中包含了该 SQL 节点内的所有信息,然后逐个遍历子节点

1. 其中文本内容会被解析成`<#text></#text>`节点,就算一个换行符也会解析成这个
   	2. `<![CDATA[ ]]>` 会被解析成 `<#cdata-section></#cdata-section>` 节点
         	3. 还有其他MyBatis自定义的标签`<if /> <where />`等等

<2.1> 如果子节点是<#text />或者<#cdata-section />类型

<2.1.1> 获取子节点的文本内容

<2.1.2> 创建 TextSqlNode 对象

<2.1.2.1> 调用 TextSqlNode 的 isDynamic() 方法,点击去该进去看看就知道了,如果文本中使用了${},则标记为动态 SQL 语句,将其添加至 contents 数组中

<2.1.2.2> 否则就是静态文本内容,创建对应的 StaticTextSqlNode 对象,将其添加至 contents 数组中

<2.2> 如果类型是 Node.ELEMENT_NODE 时,也就是 MyBatis 的自定义标签

<2.2.1> 根据子节点的标签名称,获得对应的 NodeHandler 对象

<2.2.2> 执行NodeHandlerhandleNode方法处理该节点,创建不通类型的 SqlNode 并添加到 contents 数组中,如何处理的在下面讲述

<2.2.3> 标记为动态 SQL 语句

<3> 最后将创建 contents 封装成 MixedSqlNode 对象

NodeHandler

XMLScriptBuilder的内部接口,用于处理MyBatis自定义标签,接口实现类如下图所示:

NodeHandler

代码如下:

private interface NodeHandler {
    /**
     * 处理 Node
     *
     * @param nodeToHandle   要处理的 XNode 节点
     * @param targetContents 目标的 SqlNode 数组。实际上,被处理的 XNode 节点会创建成对应的 SqlNode 对象,添加到 targetContents 中
     */
    void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}

这些 NodeHandler 实现类都定义在 XMLScriptBuilder 内部,用于处理不同标签,我们逐个来看

BindHandler

实现了NodeHandler接口,<bind />标签的处理器,代码如下:

/**
 * <bind />元素允许你在 OGNL 表达式(SQL语句)以外创建一个变量,并将其绑定到当前的上下文
 */
private class BindHandler implements NodeHandler {
    public BindHandler() {
        // Prevent Synthetic Access
    }
    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        // 解析 name、value 属性
        final String name = nodeToHandle.getStringAttribute("name");
        final String expression = nodeToHandle.getStringAttribute("value");
        // 创建 VarDeclSqlNode 对象
        final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
        targetContents.add(node);
    }
}
  1. 获取<bind />标签的name和value属性

  2. 根据这些属性创建一个 VarDeclSqlNode 对象

  3. 添加到targetContents集合中

例如这样配置:

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

TrimHandler

实现了NodeHandler接口,<trim />标签的处理器,代码如下:

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

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        // <1> 解析内部的 SQL 节点,成 MixedSqlNode 对象
        MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
        // <2> 获得 prefix、prefixOverrides、"suffix"、suffixOverrides 属性
        String prefix = nodeToHandle.getStringAttribute("prefix");
        String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
        String suffix = nodeToHandle.getStringAttribute("suffix");
        String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
        // <3> 创建 TrimSqlNode 对象
        TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
        targetContents.add(trim);
    }
}
  1. 继续调用parseDynamicTags方法解析<if />标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象

  2. 获得 prefixprefixOverridessuffixsuffixOverrides 属性

  3. 根据上面获取到的属性创建TrimSqlNode对象

  4. 添加到targetContents集合中

WhereHandler

实现了NodeHandler接口,<where />标签的处理器,代码如下:

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

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        // 解析内部的 SQL 节点,成 MixedSqlNode 对象
        MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
        // 创建 WhereSqlNode 对象
        WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
        targetContents.add(where);
    }
}
  1. 继续调用parseDynamicTags方法解析<where />标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象
  2. 创建WhereSqlNode对象,该对象继承了TrimSqlNode,自定义前缀(WHERE)和需要删除的前缀(AND、OR等)
  3. 添加到targetContents集合中

SetHandler

实现了NodeHandler接口,<set />标签的处理器,代码如下:

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

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        // 解析内部的 SQL 节点,成 MixedSqlNode 对象
        MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
        SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
        targetContents.add(set);
    }
}
  1. 继续调用parseDynamicTags方法解析<set />标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象
  2. 创建SetSqlNode对象,该对象继承了TrimSqlNode,自定义前缀(SET)和需要删除的前缀和后缀(,)
  3. 添加到targetContents集合中

ForEachHandler

实现了NodeHandler接口,<foreach />标签的处理器,代码如下:

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

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        // 解析内部的 SQL 节点,成 MixedSqlNode 对象
        MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
        // 获得 collection、item、index、open、close、separator 属性
        String collection = nodeToHandle.getStringAttribute("collection");
        String item = nodeToHandle.getStringAttribute("item");
        String index = nodeToHandle.getStringAttribute("index");
        String open = nodeToHandle.getStringAttribute("open");
        String close = nodeToHandle.getStringAttribute("close");
        String separator = nodeToHandle.getStringAttribute("separator");
        // 创建 ForEachSqlNode 对象
        ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);
        targetContents.add(forEachSqlNode);
    }
}
  1. 继续调用parseDynamicTags方法解析<foreach />标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象
  2. 获得 collection、item、index、open、close、separator 属性
  3. 根据这些属性创建ForEachSqlNode对象
  4. 添加到targetContents集合中

IfHandler

实现了NodeHandler接口,<if />标签的处理器,代码如下:

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

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        // 解析内部的 SQL 节点,成 MixedSqlNode 对象
        MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
        // 获得 test 属性
        String test = nodeToHandle.getStringAttribute("test");
        // 创建 IfSqlNode 对象
        IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
        targetContents.add(ifSqlNode);
    }
}
  1. 继续调用parseDynamicTags方法解析<if />标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象
  2. 获得 test 属性
  3. 根据这个属性创建IfSqlNode对象
  4. 添加到targetContents集合中

OtherwiseHandler

实现了NodeHandler接口,<otherwise />标签的处理器,代码如下:

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

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        // 解析内部的 SQL 节点,成 MixedSqlNode 对象
        MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
        targetContents.add(mixedSqlNode);
    }
}
  1. 继续调用parseDynamicTags方法解析<otherwise />标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象
  2. 添加到targetContents集合中,需要结合ChooseHandler使用

ChooseHandler

实现了NodeHandler接口,<choose />标签的处理器,代码如下:

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<>();
        // 解析 `<when />` 和 `<otherwise />` 的节点们
        handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
        // 获得 `<otherwise />` 的节点,存在多个会抛出异常
        SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
        // 创建 ChooseSqlNode 对象
        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) { // 处理 `<when />` 标签的情况
                handler.handleNode(child, ifSqlNodes);
            } else if (handler instanceof OtherwiseHandler) { // 处理 `<otherwise />` 标签的情况
                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;
    }
}
  1. 先逐步处理<choose />标签的<when /><otherwise />子标签们,通过组合 IfHandler 和 OtherwiseHandler 两个处理器,实现对子节点们的解析

  2. 如果存在<otherwise />子标签,则抛出异常

  3. 根据这些属性创建ChooseSqlNode对象

  4. 添加到targetContents集合中

DynamicContext

org.apache.ibatis.scripting.xmltags.DynamicContext:解析动态SQL语句时的上下文,用于解析SQL时,记录动态SQL处理后的SQL语句,内部提供ContextMap对象保存上下文的参数

构造方法

public class DynamicContext {

  /**
   * 入参保存在 ContextMap 中的 Key
   *
   * {@link #bindings}
   */
  public static final String PARAMETER_OBJECT_KEY = "_parameter";
  /**
   * 数据库编号保存在 ContextMap 中的 Key
   *
   * {@link #bindings}
   */
  public static final String DATABASE_ID_KEY = "_databaseId";

  static {
    // <1.2> 设置 OGNL 的属性访问器
    OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
  }

  /**
   * 上下文的参数集合,包含附加参数(通过`<bind />`标签生成的,或者`<foreach />`标签中的集合的元素等等)
   */
  private final ContextMap bindings;
  /**
   * 生成后的 SQL
   */
  private final StringJoiner sqlBuilder = new StringJoiner(" ");
  /**
   * 唯一编号。在 {@link org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.ForEachHandler} 使用
   */
  private int uniqueNumber = 0;

  public DynamicContext(Configuration configuration, Object parameterObject) {
    // <1> 初始化 bindings 参数
    if (parameterObject != null && !(parameterObject instanceof Map)) {
      // 构建入参的 MetaObject 对象
      MetaObject metaObject = configuration.newMetaObject(parameterObject);
      // 入参类型是否有对应的类型处理器
      boolean existsTypeHandler = configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
      bindings = new ContextMap(metaObject, existsTypeHandler);
    } else {
      bindings = new ContextMap(null, false);
    }
    // <2> 添加 bindings 的默认值
    bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
    bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
  }
}
类型 属性 说明
ContextMap bindings 上下文的参数集合,包含附加参数(通过<bind />标签生成的,或者<foreach />标签解析参数保存的),以及几个默认值
StringJoiner sqlBuilder 保存本次解析后的SQL,每次添加字符串以空格作为分隔符
int uniqueNumber 唯一编号,在ForEachHandler处理节点时需要用到,生成唯一数组作为集合中每个元素的索引(作为后缀)
  1. 初始化bindings参数,创建 ContextMap 对象
    1. 根据入参转换成MetaObject对象
    2. 在静态代码块中,设置OGNL的属性访问器,OgnlRuntime 是 OGNL 库中的类,设置ContextMap对应的访问器是ContextAccessor
  2. bindings中添加几个默认值:_parameter > 入参对象,_databaseId -> 数据库标识符

ContextMap

DynamicContext的内部静态类,继承HashMap,用于保存解析动态SQL语句时的上下文的参数集合,代码如下:

static class ContextMap extends HashMap<String, Object> {
    private static final long serialVersionUID = 2977601501966151582L;
    /**
     * parameter 对应的 MetaObject 对象
     */
    private final MetaObject parameterMetaObject;
    /**
     * 是否有对应的类型处理器
     */
    private final boolean fallbackParameterObject;

    public ContextMap(MetaObject parameterMetaObject, boolean fallbackParameterObject) {
      this.parameterMetaObject = parameterMetaObject;
      this.fallbackParameterObject = fallbackParameterObject;
    }

    @Override
    public Object get(Object key) {
      String strKey = (String) key;
      if (super.containsKey(strKey)) {
        return super.get(strKey);
      }

      if (parameterMetaObject == null) {
        return null;
      }

      if (fallbackParameterObject && !parameterMetaObject.hasGetter(strKey)) {
        return parameterMetaObject.getOriginalObject();
      } else {
        // issue #61 do not modify the context when reading
        return parameterMetaObject.getValue(strKey);
      }
    }
}

重写了 HashMap 的 get(Object key) 方法,增加支持对 parameterMetaObject 属性的访问

ContextAccessor

DynamicContext的内部静态类,实现 ognl.PropertyAccessor 接口,上下文访问器,代码如下:

static class ContextAccessor implements PropertyAccessor {
    @Override
    public Object getProperty(Map context, Object target, Object name) {
      Map map = (Map) target;

      // 优先从 ContextMap 中,获得属性
      Object result = map.get(name);
      if (map.containsKey(name) || result != null) {
        return result;
      }

      // <x> 如果没有,则从 PARAMETER_OBJECT_KEY 对应的 Map 中,获得属性
      Object parameterObject = map.get(PARAMETER_OBJECT_KEY);
      if (parameterObject instanceof Map) {
        return ((Map) parameterObject).get(name);
      }

      return null;
    }

    @Override
    public void setProperty(Map context, Object target, Object name, Object value) {
      Map<Object, Object> map = (Map<Object, Object>) target;
      map.put(name, value);
    }

    @Override
    public String getSourceAccessor(OgnlContext arg0, Object arg1, Object arg2) {
      return null;
    }

    @Override
    public String getSourceSetter(OgnlContext arg0, Object arg1, Object arg2) {
      return null;
    }
}

在DynamicContext的静态代码块中,设置OGNL的属性访问器,设置了ContextMap.class的属性访问器为ContextAccessor

这里方法的入参中的target,就是 ContextMap 对象

  1. 在重写的getProperty方法中,先从 ContextMap 里面获取属性值(可以回过去看下ContextMap的get方法)

  2. 没有获取到则获取 PARAMETER_OBJECT_KEY 属性的值,如果是 Map 类型,则从这里面获取属性值

回看 DynamicContext 的构造方法,细品一下

标签:初始化,String,private,SqlNode,源码,context,SQL,MyBatis,public
来源: https://www.cnblogs.com/lifullmoon/p/14015066.html