最近在学Mybatis,在学到动态sql的trim标签时,很迷惑。不知所以然。看别人的博客和论坛里的解释,太宽泛,还是不能理解:
trim元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是prefix和suffix;可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是prefixOverrides和suffixOverrides;正因为trim有这样的功能,所以我们也可以非常简单的利用trim来代替where元素的功能。
例1:
1 <select id="selectUsersTrim" resultMap="resultListUsers" parameterType="Users"> 2 select * from users 3 <trim prefix="where" prefixOverrides="and"> 4 <if test="name!=null"> 5 name=#{name} 6 </if> 7 <if test="address!=null"> 8 and address=#{address} 9 </if> 10 </trim> 11 </select>
可以看到后台打印的sql:
例2:
1 <select id="selectUsersTrim" resultMap="resultListUsers" parameterType="Users"> 2 select * from users 3 <trim prefix="where" prefixOverrides="and"> 4 <if test="name!=null"> 5 or name=#{name} 6 </if> 7 <if test="address!=null"> 8 and address=#{address} 9 </if> 10 </trim> 11 </select>
后台报错:
怎么办呢?遇到问题,当然要想办法解决。灵光一闪,我不是把源代码绑定到了mybatis的jar上了吗?对,那就看源代码,源代码还能debug呢,更方便。
trim标签的java方法调用栈轨迹:(只写出关键的几个)
org.apache.ibatis.scripting.xmltags.TrimSqlNode.apply()
org.apache.ibatis.scripting.xmltags.TrimSqlNode.FilteredDynamicContext.applyAll()
org.apache.ibatis.scripting.xmltags.TrimSqlNode.FilteredDynamicContext.applyPrefix()
applyPrefix()的源代码:
代码块1:
1 private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) { 2 if (!prefixApplied) { 3 prefixApplied = true; 4 if (prefixesToOverride != null) { 5 for (String toRemove : prefixesToOverride) { 6 if (trimmedUppercaseSql.startsWith(toRemove)) { 7 sql.delete(0, toRemove.trim().length()); 8 break; 9 } 10 } 11 } 12 if (prefix != null) { 13 sql.insert(0, " "); 14 sql.insert(0, prefix); 15 } 16 } 17 }
prefixApplied的值:初始是false.从下面代码内部类TrimSqlNode.FilteredDynamicContext的构造器可以看出:
代码块2:
1 public FilteredDynamicContext(DynamicContext delegate) { 2 super(configuration, null); 3 this.delegate = delegate; 4 this.prefixApplied = false; 5 this.suffixApplied = false; 6 this.sqlBuffer = new StringBuilder(); 7 }
List集合类型prefixesToOverride :trim标签的属性prefixOverrides,
String类型prefix:trim标签的属性prefix.
trimmedUppercaseSql:trim标签的子标签里的sql语句.
见下面代码:(TrimSqlNode的成员变量):
代码块3:
1 private String prefix; 2 private String suffix; 3 private List<String> prefixesToOverride; 4 private List<String> suffixesToOverride;
现在我们在回看代码块1,会发现整个方法的大致意思:当trim标签prefixOverrides属性不为空时,遍历prefixOverrides集合的值,并且用trim标签里第一个子标签(比如if标签)的sql的语句头去匹配prefixOverrides集合的元素值,一旦匹配成功,则将第一个子标签的sql语句匹配的元素删掉,继续向下运行,判断prefix属性是否有值,若有值,在将prefix的值放到第一个子标签的sql语句开头。
所以,例2,将prefixOverrides的值改为 or 或者 and|or 就行了。
好吧,就是这样,可能是自己的语言表达能力不够水平,感觉还是看代码来的精确 :)
最后在总结下吧:
trim标签的prefixOverrides和prefix分两步骤:
1.如果prefixOverrides有元素,拿元素去匹配 第一个子标签sql语句,若匹配上,就删掉sql语句的匹配部分,跳到2
2.如果prefix有值,就在 第一个子标签sql语句 的最前面加上 prefix的值。