您的位置:首页 > 数据库

mybatis学习之路----动态sql之trim标签源码详解,附带where标签解析

2017-09-26 22:36 288 查看
点滴记载,点滴进步,愿自己更上一层楼。

头一次见到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 = par
4000
ameterObject == 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的逻辑,只不过初始化的时候些许不同。不做过多的解读。
也许理解很片面,多多指教     谢谢。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息