mybatis的关于动态SQL之trim用法
文章转载自 : https://blog.csdn.net/xu1916659422/article/details/78107869
头一次见到mybatis的trim标签,完全不知怎么使用,不知道怎么使用怎么办,就只能 复制 粘贴 做一个代码搬运工。
今天有空研究了一下trim标签的用法,透过源码看本质。终于知道了它的功能。
首先说它的用法,最后进行源码看看处理逻辑。
trim有 prefix prefixOverrides suffix suffixOverrides 几个属性
其中 prefix="where" 表示会将where放到trim标签包裹的sql的开头
prefixOverrides="and |or" 表示 如果trim标签里面包裹的sql 以 and 或者or 开头,会将这条sql开头的and 或者or去掉 当然这步是在 prefix 之前执行 的。其中 “and|or” 并不是固定的写法 表示是仅仅是需要去掉的sql开头。用 | 分割,你写成 and | or | xxx也没问题只要你觉得开心就好。
suffix="," 表示trim包裹的sql的最后面会加上这个逗号,逗号不是固定的写法,写成order by xxx也ok,规则自定。
suffixOverrides=",|where" 用法其实跟prefixOverrides差不多,只不过这里控制的是sql的结尾,也是用 | 进行分割,
如果sql末尾符合其中一条就会将对应的东西接去掉,比如,如果sql最后多了个逗号,这里就会将末尾的逗号去掉,最后加上suffix的内容。
trim标签的好处。
比如下面的sql,如果对象传过来的属性中username为null 但是 password不为null
则最终的sql为 select * from t_user where and password = 参数 很明显这条sql执行错误。
<select id="selectUseIf" parameterType="com.soft.mybatis.model.DynamicSqlModel" resultMap="userMap"> select * from t_user WHERE <!--<trim prefix="where" prefixOverrides="and |or" suffix="" suffixOverrides="">--> <if test="username != null"> username=#{username} </if> <if test="password != null"> and password=#{password} </if> <!--</trim>--> </select>
如果此时将其稍加改造,
<select id="selectUseIf" parameterType="com.soft.mybatis.model.DynamicSqlModel" resultMap="userMap"> select * from t_user <trim prefix="where" prefixOverrides="and |or" suffix="" suffixOverrides=""> <if test="username != null"> username=#{username} </if> <if test="password != null"> and password=#{password} </if> </trim> </select>
因为有了上面对trim标签的叙述,可知,即使trim包裹的这段sql最后匹配结果为 and password=参数 trim 也会将and去掉 最后加上where。看看运行效果。
==> Preparing: select * from t_user where password=?
==> Parameters: 123456(String)
执行无异常。
看了效果后,下面进行源码解析。
首先mybatis是怎么解析上面的语句的呢?
大致分为两部分,一部分静态sql部分,select * from t_user
一部分动态sql部分,trim包裹的部分最后会拼装成一个sql
最后将两条sql进行拼接。
以 DynamicSqlSource中的 getBoundSql 为切入点
public BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(this.configuration, parameterObject); this.rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.configuration); Class parameterType = parameterObject == null?Object.class:parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); Iterator i$ = context.getBindings().entrySet().iterator(); while(i$.hasNext()) { Entry entry = (Entry)i$.next(); boundSql.setAdditionalParameter((String)entry.getKey(), entry.getValue()); } return boundSql; }

可以看出分成了两类sqlNode 一类静态的 一类trim的。接下来分析trimsqlnode的处理。
最后会进入 TrimSqlNode 的apply方法
public boolean apply(DynamicContext context) { TrimSqlNode.FilteredDynamicContext filteredDynamicContext = new TrimSqlNode.FilteredDynamicContext(context); boolean result = this.contents.apply(filteredDynamicContext); filteredDynamicContext.applyAll(); return result; }
其中 boolean result = this.contents.apply(filteredDynamicContext); 会将trim包裹的if标签进行匹配组装sql放入到 filteredDynamicContext中
继续看 filteredDynamicContext.applyAll();
public void applyAll() { this.sqlBuffer = new StringBuilder(this.sqlBuffer.toString().trim()); String trimmedUppercaseSql = this.sqlBuffer.toString().toUpperCase(Locale.ENGLISH); if(trimmedUppercaseSql.length() > 0) { this.applyPrefix(this.sqlBuffer, trimmedUppercaseSql); this.applySuffix(this.sqlBuffer, trimmedUppercaseSql); } this.delegate.appendSql(this.sqlBuffer.toString()); }
this.sqlBuffer = new StringBuilder(this.sqlBuffer.toString().trim()); // 上步 contents.apply 组装的sql取出 去两边空格
如果 sql不为空则进行接下来的两步。
this.applyPrefix(this.sqlBuffer, trimmedUppercaseSql);
this.applySuffix(this.sqlBuffer, trimmedUppercaseSql);
首先看 applyPrefix
private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) { if(!this.prefixApplied) { this.prefixApplied = true; if(TrimSqlNode.this.prefixesToOverride != null) { Iterator i$ = TrimSqlNode.this.prefixesToOverride.iterator(); while(i$.hasNext()) { String toRemove = (String)i$.next(); if(trimmedUppercaseSql.startsWith(toRemove)) { sql.delete(0, toRemove.trim().length()); break; } } } if(TrimSqlNode.this.prefix != null) { sql.insert(0, " "); sql.insert(0, TrimSqlNode.this.prefix); } } }
代码也很容易解读,循环prefixesToOverride里面的东西。 prefixesToOverride 如果组装的sql的开头匹配到这个里面的任意一个,直接将这个东东删除,比如,sql以and开头,
则到这里会将sql开头的and去掉,最后在开头加上 TrimSqlNode.this.prefix 你配置的需要加到开头的字符串。
接下来看看applySuffix
private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) { if(!this.suffixApplied) { this.suffixApplied = true; if(TrimSqlNode.this.suffixesToOverride != null) { label33: { Iterator i$ = TrimSqlNode.this.suffixesToOverride.iterator(); String toRemove; do { if(!i$.hasNext()) { break label33; } toRemove = (String)i$.next(); } while(!trimmedUppercaseSql.endsWith(toRemove) && !trimmedUppercaseSql.endsWith(toRemove.trim())); int start = sql.length() - toRemove.trim().length(); int end = sql.length(); sql.delete(start, end); } } if(TrimSqlNode.this.suffix != null) { sql.append(" "); sql.append(TrimSqlNode.this.suffix); } } }
代码也很好理解。除了 label33: 这个东西可能有些人不知道,这是java的标签语言,具体可以百度。
相信其他的都能看懂。不多做解读。
可以自己多debug跟踪下。
where标签
mybatis还有一个where标签。来看看where标签对应的解析类。
WhereSqlNode
public class WhereSqlNode extends TrimSqlNode { private static List<String> prefixList = Arrays.asList(new String[]{"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, (String)null, (List)null); } }
看到这里明悟了,处理逻辑还是trimSqlNode的逻辑,只不过初始化的时候些许不同。不做过多的解读。
感觉这就是把mapper.xml 给 javaBean 化了。

浙公网安备 33010602011771号