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.观察下列代码,进行流程分析。

  1. 在第一个trim标签中,存在prefix属性,那么语句拼凑为 where id=#{id}
  2. 检查suffix和suffixOverrides,suffixOverrides值为空,那么不做任何处理,此时会继续拼接suffix的值,所以sql语句变成where id=#{id} and
  3. 在第二个trim标签中,存在 prefixOverrides="and|or|where",mybatis首先使用竖线'|'分割为数组,因为1=1 and并不以{and,or,where}开头,所以不做任何处理
  4. 在 suffixOverrides="and|or|where"中能够匹配到 1=1 and中的 and后缀,那么删除and,sql片段变为1=1
  5. 拼接三段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
", "OR
", "AND
", "OR
", "AND	", "OR	");

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

}
原文地址:https://www.cnblogs.com/zhengzuozhanglina/p/11307357.html