Spring mybatis源码篇章-NodeHandler实现类具体解析保存Dynamic sql节点信息

前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-XMLLanguageDriver解析sql包装为SqlSource

前文主要分析了通过mybatis的默认XML解析驱动类XMLLanguageDriver,将CRUD节点解析为各式的SqlNode
本文将在前文的基础上具体分析select|update|insert|delete节点内的其他节点是如何被解析的,例如trim/where/set等嵌套节点

SqlNode

优先观察下公共接口类SqlNode,代码如下

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

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

TextSqlNode

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

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

或者

<select id="test">
    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;
  }

关于上述代码中的GenericTokenParser是如何解析sql的,笔者就不展开了,大意上是对含有${}字符的值进行相应的绑定替换,从而生成完整的sql保存至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属性,主要保存多种类型的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,笔者放在下述的板块结合NodeHandler来一起讲解

NodeHandler

主要应用于对trim/where/set/if等类型为ELEMENT节点的补充解析。
其是org.apache.ibatis.scripting.xmltags.XMLScriptBuilder的内部类

  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标签节点

  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);
    }
  }

VarDeclSqlNode

public class VarDeclSqlNode implements SqlNode {

  private final String name;
  private final String expression;

  public VarDeclSqlNode(String var, String exp) {
    name = var;
    expression = exp;
  }

  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);
    }
  }

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\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");

  public WhereSqlNode(Configuration configuration, SqlNode contents) {
    super(configuration, contents, "WHERE", prefixList, null, null);
  }

}

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

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. whereset标签均可看做是trim标签的子类
  2. choosewhen/otherwise搭配使用,可以有多个when标签,只允许至多单个otherwise标签
  3. 除了bind标签,其余标签底下均可以有多个其他标签
  4. TextSqlNode/StaticTextSqlNode可以说是CRUD解析sql的基础类

下节预告

XML配置方式的具体SQL解析我们已经了解的差不多了,那我们必须了解如何让java接口类与XML定义的namespace属性相关联。
这就必须引出Spring的MapperScannerConfiguer,详见后文分析

转载于:https://www.cnblogs.com/question-sky/p/6642263.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值