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的逻辑,只不过初始化的时候些许不同。不做过多的解读。
也许理解很片面,多多指教 谢谢。
头一次见到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的逻辑,只不过初始化的时候些许不同。不做过多的解读。
也许理解很片面,多多指教 谢谢。
相关文章推荐
- mybatis的动态SQL(三)where、set、trim标签的使用
- MyBatis代码实例系列-06:Mybatis动态SQL标签(一)---if、where、set、trim、choose
- Mybatis动态SQL之if、choose、where、set、trim、foreach标记实例详解
- Mybatis 动态SQL之<trim>,<where>,<set>源码解析
- mybatis动态sql中的trim标签的使用
- mybatis动态sql中的trim标签的使用
- My Batis mapper.xml中 动态SQL中使用trim标签 if end的场景
- mybatis动态sql中的trim标签的使用
- Mybatis中动态SQL,if,where,foreach的使用教程详解
- MyBatis动态SQL中trim标签的使用
- Spark源码系列(九)Spark SQL初体验之解析过程详解
- mybatis动态sql中的trim标签的使用
- mybatis中动态SQL之trim详解
- 通过源码分析MyBatis的缓存/Mybatis解析动态sql原理分析
- MyBatis动态SQL中trim标签的使用
- mybatis动态sql中的trim标签的使用
- mybatis动态sql中的trim标签的使用
- MyBatis源码(四)之mapper文件解析和动态Sql解析启动阶段
- mybatis动态sql中的trim标签的使用
- mybatis动态sql中的trim标签的使用