*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
background: #F0F0F0;
}
/* Base color: saturation 0; */
.hljs,
.hljs-subst {
color: #444;
}
.hljs-comment {
color: #888888;
}
.hljs-keyword,
.hljs-attribute,
.hljs-selector-tag,
.hljs-meta-keyword,
.hljs-doctag,
.hljs-name {
font-weight: bold;
}
/* User color: hue: 0 */
.hljs-type,
.hljs-string,
.hljs-number,
.hljs-selector-id,
.hljs-selector-class,
.hljs-quote,
.hljs-template-tag,
.hljs-deletion {
color: #880000;
}
.hljs-title,
.hljs-section {
color: #880000;
font-weight: bold;
}
.hljs-regexp,
.hljs-symbol,
.hljs-variable,
.hljs-template-variable,
.hljs-link,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #BC6060;
}
/* Language color: hue: 90; */
.hljs-literal {
color: #78A960;
}
.hljs-built_in,
.hljs-bullet,
.hljs-code,
.hljs-addition {
color: #397300;
}
/* Meta color: hue: 200 */
.hljs-meta {
color: #1f7199;
}
.hljs-meta-string {
color: #4d99bf;
}
/* Misc effects */
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
-->
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); } }
原文地址:https://www.cnblogs.com/zhengzuozhanglina/p/11307357.html