ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

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

2020-11-23 21:33:12  阅读:379  来源: 互联网

标签:初始化 String private SqlNode 源码 context SQL MyBatis public


该系列文档是本人在学习 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

主要涉及到的类:

  • org.apache.ibatis.scripting.xmltags.XMLLanguageDriver:语言驱动接口的默认实现,创建ParameterHandler参数处理器对象和SqlSource资源对象

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

  • org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.NodeHandler:定义在XMLScriptBuilder内部的一个接口,用于处理MyBatis自定义标签(<if /> <foreach />等),生成对应的SqlNode对象,不同的实现类处理不同的标签

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

  • org.apache.ibatis.scripting.xmltags.SqlNode:SQL Node接口,每个XML Node会解析成对应的SQL Node对象,通过上下文可以对动态SQL进行逻辑处理,生成需要的结果

  • org.apache.ibatis.scripting.xmltags.OgnlCache:用于处理Ognl表达式

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

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有