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

版权声明:原创文章,相互提高。 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的逻辑,只不过初始化的时候些许不同。不做过多的解读。

也许理解很片面,多多指教     谢谢。



猜你喜欢

转载自blog.csdn.net/xu1916659422/article/details/78107869
今日推荐