Mybatis源码分析:trim标签
Posted zhengzuozhanglina
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis源码分析:trim标签相关的知识,希望对你有一定的参考价值。
trim标签使用
标签用于对标签内的sql语句进行前后缀补齐或者前后缀删除。该标签提供了四个属性,prefix,prefixOverrides,suffix,suffixOverrides。prefix,suffix用于补齐sql前后缀的值。而prefixOverrides,suffixOverrides则表示如果sql语句中前缀或者后缀的值跟两个属性中的值一致,则会将这个值删除掉。看下面一段Mapper配置,该语句最终会变成select id,name,age from sstudent where id=? and 1=1.观察下列代码,进行流程分析。
- 在第一个trim标签中,存在prefix属性,那么语句拼凑为 where id=#id
- 检查suffix和suffixOverrides,suffixOverrides值为空,那么不做任何处理,此时会继续拼接suffix的值,所以sql语句变成where id=#id and
- 在第二个trim标签中,存在 prefixOverrides="and|or|where",mybatis首先使用竖线‘|‘分割为数组,因为1=1 and并不以and,or,where开头,所以不做任何处理
- 在 suffixOverrides="and|or|where"中能够匹配到 1=1 and中的 and后缀,那么删除and,sql片段变为1=1
- 拼接三段sql代码,最后结果为:select id,name,age from sstudent where id=? and 1=1
<!--测试Trim用法 --> <select id="getStudentsByTrim" resultType="student" useCache="false" parameterType="string"> select id,name,age from student <if test="#id !=null"> <trim prefix="where" suffix="and" suffixOverrides=""> id=#id </trim> <trim prefixOverrides="and|or|where" prefix="" suffixOverrides="and|or|where"> 1=1 and </trim> </if> </select>
trim标签原理实现
mybatis配置文件中存在,,,,等标签,在mybatis代码中存在与之对应的实体类,在mybatis中,存在SqlNode接口,该接口下存在多个与mapper配置文件对应的实体类。观察如下结构图,可以看到TrimSqlNode下存在SetSqlNode和WhereSqlNode,这意味着where和set标签可能仍然采用前缀拼接方式组装sql语句。
分析TrimSqlNode源码
TrimSqlNode类中有五个属性,对应这当前的节点类型,前后缀属性值和Configuration类,在TrimSqlNode构造器中,调用了parseOverrides()方法进行解析,可以看到,该方法会将待匹配的prefixesToOverride值按|进行分割并放入list中。
1 private final SqlNode contents;//当前节点 2 private final String prefix;//前缀名 3 private final String suffix;//后缀名 4 private final List<String> prefixesToOverride;//待覆盖前缀 5 private final List<String> suffixesToOverride;//待覆盖后缀 6 private final Configuration configuration; 7 8 public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) 9 this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride)); 10 11 12 /** 13 * 按竖线切割字符串 14 * @param overrides 15 * @return 16 */ 17 private static List<String> parseOverrides(String overrides) 18 if (overrides != null) 19 //字符串分析器,由JDK1.0提供,作用跟split()方法相同 20 final StringTokenizer parser = new StringTokenizer(overrides, "|", false); 21 final List<String> list = new ArrayList<String>(parser.countTokens()); 22 while (parser.hasMoreTokens()) 23 list.add(parser.nextToken().toUpperCase(Locale.ENGLISH)); 24 25 return list; 26 27 return Collections.emptyList(); 28
在一切属性都填充完毕后,TrimSqlNode就开始正式使用下列方法解析和组装sql语句了。
@Override
public boolean apply(DynamicContext context)
FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
boolean result = contents.apply(filteredDynamicContext);
filteredDynamicContext.applyAll();
return result;
可以看到该方法中实例化了FilteredDynamicContext类,并调用了该类中的applyAll()方法,applyAll()方法中存在applyPrefix(),applySuffix()专门用来解析trim标签的属性。这两个方法类似,先遍历分割好的prefixesToOverride或者suffixesToOverride的值,如果待解析的sql存在要删除的前缀或者后缀,则调用delete方法进行删除,然后跳出该循环,这也就是说最多只能删除一个后缀。在完成前后缀删除后,再分别进行前后缀拼接。
private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) if (!prefixApplied) prefixApplied = true; if (prefixesToOverride != null) for (String toRemove : prefixesToOverride) if (trimmedUppercaseSql.startsWith(toRemove)) sql.delete(0, toRemove.trim().length()); break; if (prefix != null) sql.insert(0, " "); sql.insert(0, prefix); private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) if (!suffixApplied) suffixApplied = true; if (suffixesToOverride != null) for (String toRemove : suffixesToOverride) if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) int start = sql.length() - toRemove.trim().length(); int end = sql.length(); sql.delete(start, end); break; if (suffix != null) sql.append(" "); sql.append(suffix);
where 和Set标签
从SqlNode的继承关系可以看到,WhereSqlNode和SetSqlNode都继承了TrimSqlNode,猜想这两个类是否直接调用了父类方法,采用applyFrefix()进行前缀拼接,查看这两个类中的源码。可以看到确实调用了父类相关方法,如果使用set,在执行update操作时,会自动补全set关键字,同时补全后缀符‘,‘,如果使用where,在执行条件查询时,会自动补全where关键字,如果后续sql语句前缀带有AND,OR类似关键字,则删除sql前缀。
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); 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);
以上是关于Mybatis源码分析:trim标签的主要内容,如果未能解决你的问题,请参考以下文章