• Spring mybatis源码篇章-动态SQL节点源码深入


    通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-动态SQL基础语法以及原理

    前话

    前文描述到通过mybatis默认的解析驱动类org.apache.ibatis.scripting.xmltags.XMLLanguageDriver,将mapper文件的CRUD节点均解析成对应的SqlNode接口。

    本文将在前文的基础上具体分析select|update|insert|delete节点内的其他节点是如何被解析的,例如trim/where/set等嵌套节点,算是深入下mybatis的源码

    SqlNode

    优先观察下公共接口类org.apache.ibatis.scripting.xmltags.SqlNode,代码如下

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

    内部只有一个apply()方法,其应该会在上下文context操刀,我们先可以看下有哪些类型的SqlNode

    TextSqlNode

    对类型为CDATA块或者TEXT的包装为TextSqlNode对象,形如

    <select id="testQuery">
    	<![CDATA[
    		select * from tb_test
    	]]>
    </select>
    

    或者

    <select id="testQuery" parameterType="java.lang.String">
    	select * from tb_test where name = #{name}
    </select>
    

    笔者此处看下其是如何实现apply()方法把,代码如下

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

    关于如何解析相关的SQL语句就不展开了,大意上是针对含有${}符号的字符串进行相应的替换,从而拼装成完整的SQL保存至相应的org.apache.ibatis.scripting.xmltags.DynamicContext上下文对象中


    另外此类还有一个关键的方法isDynamic(),其是为了判断相应的字符串是否为动态SQL

    public boolean isDynamic() {
        DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
    	// same as
        GenericTokenParser parser = createParser(checker);
        parser.parse(text);
    	// true while the text contains '${}'
        return checker.isDynamic();
      }
    

    只有SQL语句含有${}标志符号,才会返回true

    StaticTextSqlNode

    保存无特殊字符${}的SQL语句,即其是建立在上述的TextSqlNode#isDynamic()方法的基础上,在返回为false的情况下被包装。
    apply()方法也特别的简单

     public boolean apply(DynamicContext context) {
        context.appendSql(text);
        return true;
      }
    

    MixedSqlNode

    其内部只有一个类型为java.util.Listcontents属性,主要作用是保存一个CRUD节点下所含有的所有SqlNode集合,方便统一管理以及解析

    public class MixedSqlNode implements SqlNode {
      private List<SqlNode> contents;
    
      public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
      }
    
      public boolean apply(DynamicContext context) {
        for (SqlNode sqlNode : contents) {
          sqlNode.apply(context);
        }
        return true;
      }
    }
    

    其他类型的SqlNode基本都是基于MixedSqlNode来进行包装解析的,而它们基本都有对应的NodeHandler类来与之对应解析

    NodeHandler

    其是org.apache.ibatis.scripting.xmltags.XMLScriptBuilder的私有内部接口类,而相应的动态SQL节点都是提前被保存在了下述代码中的nodeHandlers变量中

      private Map<String, NodeHandler> nodeHandlers = new HashMap<String, NodeHandler>() {
        private static final long serialVersionUID = 7123056019193266281L;
    
        {
          put("trim", new TrimHandler());
          put("where", new WhereHandler());
          put("set", new SetHandler());
          put("foreach", new ForEachHandler());
          put("if", new IfHandler());
          put("choose", new ChooseHandler());
          put("when", new IfHandler());
          put("otherwise", new OtherwiseHandler());
          put("bind", new BindHandler());
        }
      };
    

    BindHandler

    用于解析bind标签节点
    例如

    <bind name="title" value="'%' + _parameter.getTitle() + '%'"/>
    

    对应的解析语句如下

      private class BindHandler implements NodeHandler {
        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);
        }
      }
    

    会包装成org.apache.ibatis.scripting.xmltags.VarDeclSqlNode节点进行相应的解析


    VarDeclSqlNode#apply()

    public boolean apply(DynamicContext context) {
        final Object value = OgnlCache.getValue(expression, context.getBindings());
        context.bind(name, value);
        return true;
    }
    

    很明显就是将name和计算后的真实value值对应关系保存至DynamicContext#bindings属性(HashMap类型)中

    TrimHandler

    用于解析trim标签节点

      private class TrimHandler implements NodeHandler {
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    	  //trim标签下可包含where/set/if/when等标签,将之封装成MixedSqlNode
          List<SqlNode> contents = parseDynamicTags(nodeToHandle);
          MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
    	  // read prefix/preffixOverrides/suffix/suffixOverrides properties
          String prefix = nodeToHandle.getStringAttribute("prefix");
          String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
          String suffix = nodeToHandle.getStringAttribute("suffix");
          String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
    	  // delegate TrimSqlNode to process trim sql
          TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
          targetContents.add(trim);
        }
      }
    

    直接包装成org.apache.ibatis.scripting.xmltags.TrimSqlNode对象


    TrimSqlNode
    首先观察下构造函数

    public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
      this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
    }
    
    protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {
       //这里contents一般为MixedSqlNode,内部包含多个SqlNode
        this.contents = contents;
        this.prefix = prefix;
        this.prefixesToOverride = prefixesToOverride;
        this.suffix = suffix;
        this.suffixesToOverride = suffixesToOverride;
        this.configuration = configuration;
      }
    

    读取的preffixOverrides/suffixOverrides属性可以是符合以|为分隔符的字符串,比如"and | or"会被解析为List["AND","OR"]形式

    笔者此处再简单看下apply()方法,代码如下

      public boolean apply(DynamicContext context) {
        FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
        // first,parse nested nodes
        boolean result = contents.apply(filteredDynamicContext);
        // aim to prefixOverrides and suffixOverrides,generate corrent sql
        filteredDynamicContext.applyAll();
        return result;
      }
    
    

    经过上述的代码执行后,生成的SQL语句均是转为大写形式的~~~

    WhereHandler

    用于解析where标签节点

      private class WhereHandler implements NodeHandler {
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
          List<SqlNode> contents = parseDynamicTags(nodeToHandle);
          MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
          // same as TrimSqlNode
          WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
          targetContents.add(where);
        }
      }
    

    WhereSqlNode
    经过查看,其是TrimSqlNode的子类,简单看下其源码

    public class WhereSqlNode extends TrimSqlNode {
    
      private static List<String> prefixList = Arrays.asList("AND ","OR ","AND
    ", "OR
    ", "AND
    ", "OR
    ", "AND	", "OR	");
    
      public WhereSqlNode(Configuration configuration, SqlNode contents) {
        super(configuration, contents, "WHERE", prefixList, null, null);
      }
    
    }
    

    很简单,就是固定了参数prefix=WHEREprefixOverrides=List["AND ","OR ","AND ", "OR ", "AND ", "OR ", "AND ", "OR "],其余调用父类方法即可,详见上文

    SetHandler

    用于解析set标签节点

      private class SetHandler implements NodeHandler {
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
          List<SqlNode> contents = parseDynamicTags(nodeToHandle);
          MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
          // same as TrimSqlNode
          SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
          targetContents.add(set);
        }
      }
    

    SetSqlNode
    经过查看,其也是TrimSqlNode的子类,简单看下其源码

    public class SetSqlNode extends TrimSqlNode {
    
      private static List<String> suffixList = Arrays.asList(",");
    
      public SetSqlNode(Configuration configuration,SqlNode contents) {
        super(configuration, contents, "SET", null, null, suffixList);
      }
    
    }
    

    很简单,就是固定了参数prefix=SETsuffix=null(会将suffixOverrides符合的条件置为空)、suffixOverrides=List[","],其余调用父类方法即可,详见上文

    ForEachHandler

    用于解析foreach标签节点

      private class ForEachHandler implements NodeHandler {
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
          List<SqlNode> contents = parseDynamicTags(nodeToHandle);
          MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
          // read collection/item/index/open/close/separator properties
          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");
          // independent SqlNode
          ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);
          targetContents.add(forEachSqlNode);
        }
      }
    

    foreach节点的属性简析如下

    • collection 代表的是集合的类型,例如list代表参数类型为List.class,array代表参数类型为数组类型
    • item 代表集合的value值
    • index 代表集合的key值,可为下标值也可为HashMap中的key值
    • open 类似于prefix属性
    • close 类似于suffix属性
    • separator 拼装的分隔符号,多为","

    ForEachSqlNode
    独立的类,限于比较复杂,笔者此处只查看下其构造函数,有兴趣的读者可自行分析

      public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, String index, String item, String open, String close, String separator) {
        //解析帮助类
        this.evaluator = new ExpressionEvaluator();
        // 集合别名
        this.collectionExpression = collectionExpression;
        this.contents = contents;
        this.open = open;
        this.close = close;
        this.separator = separator;
        this.index = index;
        this.item = item;
        this.configuration = configuration;
      }
    

    IfHandler

    用于解析if标签节点

      private class IfHandler implements NodeHandler {
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
          List<SqlNode> contents = parseDynamicTags(nodeToHandle);
          MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
          // read test properties
          String test = nodeToHandle.getStringAttribute("test");
          // 
          IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
          targetContents.add(ifSqlNode);
        }
      }
    

    IfSqlNode
    条件判断解析类,内部代码很简单,如下

    public class IfSqlNode implements SqlNode {
      private ExpressionEvaluator evaluator;
      private String test;
      private SqlNode contents;
    
      public IfSqlNode(SqlNode contents, String test) {
        this.test = test;
        this.contents = contents;
        this.evaluator = new ExpressionEvaluator();
      }
    
      public boolean apply(DynamicContext context) {
        //主要作用即是用于条件的判断
        if (evaluator.evaluateBoolean(test, context.getBindings())) {
          contents.apply(context);
          return true;
        }
        return false;
      }
    
    }
    

    org.apache.ibatis.scripting.xmltags.ExpressionEvaluator主要是应用OGNL语法进行解析类似name !=null,其会读取上下文中是否有对应的属性值。具体的读者可自行分析

    OtherwiseHandler/ChooseHandler

    用于解析otherwise/choose/when节点,这三者一般搭配使用
    1.OtherwiseHandler

      private class OtherwiseHandler implements NodeHandler {
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
          List<SqlNode> contents = parseDynamicTags(nodeToHandle);
          MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
          targetContents.add(mixedSqlNode);
        }
      }
    

    2.ChooseHandler,内含choose/when的解析

      private class ChooseHandler implements NodeHandler {
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
          List<SqlNode> whenSqlNodes = new ArrayList<SqlNode>();
          List<SqlNode> otherwiseSqlNodes = new ArrayList<SqlNode>();
          //解析choose...when..otherwise结构
          handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
          //检查otherwise标签是否只有一个,大于一个则报错
          SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
          ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
          targetContents.add(chooseSqlNode);
        }
        
        // when标签使用IfHandler解析,otherwise标签使用OtherwiseHandler
        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 = nodeHandlers.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;
        }
      }
    

    ChooseSqlNode
    简单看下其构造函数

    public class ChooseSqlNode implements SqlNode {
      private SqlNode defaultSqlNode;
      private List<SqlNode> ifSqlNodes;
    
      public ChooseSqlNode(List<SqlNode> ifSqlNodes, SqlNode defaultSqlNode) {
        this.ifSqlNodes = ifSqlNodes;
        this.defaultSqlNode = defaultSqlNode;
      }
    
      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;
      }
    }
    

    ChooseSqlNode存放多个IfSqlNode和单个TextSqlNode/StaticTextSqlNode,
    choose..when..otherwise结构类似于java的switch..case结构

    总结

    对本文提及的内容作下简单的小结

    1. where/set标签均可看做是trim标签的子类

    2. choosewhen/otherwise搭配使用,可以有多个when标签,只允许至多单个otherwise标签

    3. 除了bind标签,其余标签底下均可以有多个其他标签

    4. TextSqlNode/StaticTextSqlNode可以说是CRUD解析sql的基础类

    5. _parameter属性表示DAO接口方法对应的入参

  • 相关阅读:
    kubernetes 笔记 1
    Linux 性能优化笔记 --CPU总结
    Linux 性能优化笔记 --CPU
    RedHat OpenShift QuickStart 2.4 容器主机
    docker 常用命令
    RedHat OpenShift QuickStart 2.3 容器注册
    SpringBoot自动配置源码
    RedHat OpenShift QuickStart 2.2 容器镜像
    RedHat OpenShift QuickStart 2.1 容器介绍
    RedHat OpenShift QuickStart 1.2 容器中传入/出文件
  • 原文地址:https://www.cnblogs.com/question-sky/p/6642263.html
Copyright © 2020-2023  润新知