关于mybatis中的动态标签(常用的foreach、if、choose等),都会有对应的类去解析;SqlNode是顶级解析接口,各动态标签实现该接口的apply方法完成各自解析操作。foreach标签对应的解析实现类是foreachSqlNode.
foreach标签解析的过程就是对foreach标签中的各个属性(collection、index、item等)进行解析处理的过程,每个属性都有对应的解析处理逻辑.sql解析完成之后执行对应的查询或删除等数据操作,然后封装结果集.本文仅对foreach标签解析进行说明.
dao接口:
List<News> findNews(@Param("ids") List<Integer> ids);
xml配置文件:
<select id="findNews" resultType="com.it.txm.demo.News"> select id,title from find_news where id in ( <foreach collection="ids" item="item" separator=","> #{item} </foreach> ) </select>
测试demo:
ArrayList<Integer> list1 = new ArrayList<>(); list1.add(777); list1.add(797); List<News> list = newsMapper.findNews(list1); System.out.println(list);
debug调试进入获取BoundSql(即解析sql)
SqlNode为动态标签的顶级接口,有以下实现类.
MixedSqlNode可以认为是多个动态标签的组合处理者,会循环处理每个标签.源码:
public class MixedSqlNode implements SqlNode { private final List<SqlNode> contents; // 创建MixedSqlNode对象时会将实现sqlNode实现类集合添加到MixedSqlNode中 public MixedSqlNode(List<SqlNode> contents) { this.contents = contents; } // MixedSqlNode对apply方法的实现处理就是执行对各个标签的apply实现逻辑. @Override public boolean apply(DynamicContext context) { contents.forEach(node -> node.apply(context)); return true; } }
具体到demo中,mixedSqlNode
有两个sqlNode接口的实现类:
StaticTextSqlNode
、ForEachSqlNode
;
StaticTextSqlNode
实现apply做的处理是对sql的拼接,源码如下:
public class StaticTextSqlNode implements SqlNode { private final String text; public StaticTextSqlNode(String text) { this.text = text; } // 动态上下文对象进行拼接sql @Override public boolean apply(DynamicContext context) { context.appendSql(text); return true; } // 最终是利用sqlBuilder进行参数拼接 public void appendSql(String sql) { sqlBuilder.add(sql); } }
具体到案例中StaticTextSqlNode
实际解析的就是下面标注的两行sql:
以下说明与源码中实际解析执行的一致:
下面主要说ForEachSqlNode
,源码如下:
public boolean apply(DynamicContext context) { // 从动态上下文中获取映射绑定信息(这里只用到参数映射信息.) Map<String, Object> bindings = context.getBindings(); // 解析foreach标签中的collection属性.返回迭代器类型参数,实际就是解析的collection标签中集合的具体参数值:777 797 final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings); if (!iterable.iterator().hasNext()) { return true; } boolean first = true; // 解析foreach标签中的open属性.具体解析处理方式就是sql拼接,由于比较简单,这里不再展开. applyOpen(context); int i = 0; // 遍历迭代器中的实参. for (Object o : iterable) { DynamicContext oldContext = context{ // 此处处理foreach标签中的separator分隔符属性,new PrefixedContext()实际上执行的是DynamicContext中设置分割属性.如果 不为空则设置为foreach标签中实际使用的分割符为空则设置空字符串,执行demo中使用的是逗号. if (first || separator == null) { context = new PrefixedContext(context, ""); } else { context = new PrefixedContext(context, separator); } int uniqueNumber = context.getUniqueNumber(); // Issue #709 if (o instanceof Map.Entry) { @SuppressWarnings("unchecked") Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o; applyIndex(context, mapEntry.getKey(), uniqueNumber); applyItem(context, mapEntry.getValue(), uniqueNumber); } else { // applyIndex处理的是foreach标签中index属性,下面会展开说 applyIndex(context, i, uniqueNumber); // applyItem主要作用是解析foreach标签中的item属性并进行赋值,下面会展开说明 applyItem(context, o, uniqueNumber); } // 将foreach标签中解析完成的单个参数进行拼接 contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber)); if (first) { first = !((PrefixedContext) context).isPrefixApplied(); } context = oldContext; i++; } // 执行foreach标签中close属性对应处理,实际是执行参数拼接. applyClose(context); context.getBindings().remove(item); context.getBindings().remove(index); return true; }
简单说一下DynamicContext,可以理解为是动态参数的上下文对象,里面map形式的方法参数、用于sql拼接的sqlBuilderd.
applyIndex主要作用是处理foreach标签中的index属性,按照map形式往DynamicContext动态上下文中存入对象信息,最终封装数:context.bind中执行bindings.put(name, value);
private void applyIndex(DynamicContext context, Object o, int i) { // 对于foreach标签中index属性设置的情况下执行以下逻辑. if (index != null) { // 按照index为key,实际参数o的形式组装map存入DynamicContext动态sql上下文中. context.bind(index, o); // itemizeItem作用参数拼装,foreach中解析之后的格式默认:__frch_ + item + "_" + i,然后按照key-value形式存入DynamicContext动态sql上下文中. context.bind(itemizeItem(index, i), o); } }
applyItem执行原理同applyIndex,foreach标签中item属性不为空时执行:按照key-value形式往DynamicContext动态上下文中存入对象信息.
private void applyItem(DynamicContext context, Object o, int i) { if (item != null) { context.bind(item, o); context.bind(itemizeItem(item, i), o); } }
执行完成之后DynamicContext中的sql拼接对象中组装的查询sql如下:
至此foreach标签解析分析完成.
欢迎小伙伴评论区留言,共同交流共同进步!