zoukankan      html  css  js  c++  java
  • mybatis中动态SQL之trim详解

    一. 背景

      之前mybatis中<where>、<update>、<if>、<foreach>标签用的多,知道有<trim>这个标签,但很少去用,也没有去深入理解它,直到最近遇到一个问题。问题是这样的:

      一个SQL有三个int查询字段a、b、c,表达式为:a=#{a} AND (b=#{b} OR c=#{c})。其中a是必查的,b和c为非必查的(这里假定传入-1表示该字段不参与查询)。那么该表达式会有以下几种形态:

    • a=#{a}
    • a=#{a} AND b=#{b}
    • a=#{a} AND c=#{c}
    • a=#{a} AND (b=#{b} OR c=#{c})

      看到这个需求后,觉得逻辑还是挺简单的,但写起mapper的SQL来并不是那么容易(如果你有的话,欢迎下边评论贴出来)。考虑了多层<if>、<choose>等标签,虽然也能实现这个功能,但过于繁琐。有没有一种更简单的实现方式?有!<trim>!!!

      请尊重作者劳动成果,转载请标明原文链接:http://www.cnblogs.com/waterystone/p/7070836.html

    二. 功能描述与用法

      网上关于<trim>的介绍并不多,通过看mybatis的源码,一句话描述trim的功能:子句首尾的删除与添加。它就是一个字符串处理工具,类似于replace(),但它只处理首尾。真的是一个神器,其实mybatis中的<set>和<where>都可以用<trim>来实现,但<trim>的功能更强大,使用起来更灵活!!!

      <trim prefix="(" prefixOverrides="OR" suffixOverrides="," suffix=")">子句</trim>

      这里的子句会对其进行trim()处理,忽略掉换行、空格等字符,所以本文中的子句都是指trim()处理后的字符串。如果子句为空,那么整个<trim>块不起作用,相当于不存在。本<trim>块的作用就是:去掉子句首的OR和子句尾的逗号,并在子句前后分别加上(和),比如,"orabc,"-->"(abc)"。

    • prefixOverrides:子句首的命中词列表,以|分隔,忽略大小写。如果命中(轮询命中词,最多只命中一次),会删除子句首命中的词;没命中就算了。
    • prefix:删除子句句首后,在子句最前边加上单个空格+prefix。
    • suffixOverrides:子句尾的命中词列表,以|分隔,忽略大小写。如果命中(轮询命中词,最多只命中一次),会删除子句尾命中的词;没命中就算了。
    • suffix:删除子句句尾后,在子句最后边加上单个空格+suffix。

      有了这个神器,处理前文提起的需求,就可以用<trim>很悠然的处理了。 

    WHERE a = #{a}
    	<trim prefix="AND(" prefixOverrides="OR" suffix=")">
    		<if test="b != -1">
    			OR b = #{b}
    		</if>
    		<if test="c != -1">
    			OR c = #{c}
    		</if>
    	</trim>
    

     

      <trim>的实现非常简单,如果觉得我描述的还不够清楚,可以看下第三部分的源码。

    三.源码(org.apache.ibatis.scripting.xmltags.TrimSqlNode) 

    /**
     *    Copyright 2009-2015 the original author or authors.
     *
     *    Licensed under the Apache License, Version 2.0 (the "License");
     *    you may not use this file except in compliance with the License.
     *    You may obtain a copy of the License at
     *
     *       http://www.apache.org/licenses/LICENSE-2.0
     *
     *    Unless required by applicable law or agreed to in writing, software
     *    distributed under the License is distributed on an "AS IS" BASIS,
     *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     *    See the License for the specific language governing permissions and
     *    limitations under the License.
     */
    package org.apache.ibatis.scripting.xmltags;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.Locale;
    import java.util.Map;
    import java.util.StringTokenizer;
    
    import org.apache.ibatis.session.Configuration;
    
    /**
     * @author Clinton Begin
     */
    public class TrimSqlNode implements SqlNode {
    
      private SqlNode contents;
      private String prefix;//前缀
      private String suffix;//后缀
      private List<String> prefixesToOverride;//子句首命中词列表
      private List<String> suffixesToOverride;//子句尾命中词列表
      private Configuration configuration;
    
      public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
        this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));//解析以|分隔的命中词
      }
    
      protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {
        this.contents = contents;
        this.prefix = prefix;
        this.prefixesToOverride = prefixesToOverride;
        this.suffix = suffix;
        this.suffixesToOverride = suffixesToOverride;
        this.configuration = configuration;
      }
    
      @Override
      public boolean apply(DynamicContext context) {
        FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
        boolean result = contents.apply(filteredDynamicContext);
        filteredDynamicContext.applyAll();//解析<trim>
        return result;
      }
    
      //将命中词以|分隔,获取命中词列表
      private static List<String> parseOverrides(String overrides) {
        if (overrides != null) {
          final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
          final List<String> list = new ArrayList<String>(parser.countTokens());
          while (parser.hasMoreTokens()) {
            list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));//命中词统一转为大写。
          }
          return list;
        }
        return Collections.emptyList();
      }
    
      private class FilteredDynamicContext extends DynamicContext {
        private DynamicContext delegate;
        private boolean prefixApplied;//前缀处理标记,表示是否处理过。
        private boolean suffixApplied;//后缀处理标记,表示是否处理过。
        private StringBuilder sqlBuffer;
    
        public FilteredDynamicContext(DynamicContext delegate) {
          super(configuration, null);
          this.delegate = delegate;
          this.prefixApplied = false;
          this.suffixApplied = false;
          this.sqlBuffer = new StringBuilder();
        }
    
        public void applyAll() {
          sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());//子句先trim()
          String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);//跟命中词一致,子句也统一转为大写,所以<trim>对子句的处理是忽略大小写的。
          if (trimmedUppercaseSql.length() > 0) {//如果子句非空才处理
            applyPrefix(sqlBuffer, trimmedUppercaseSql);//处理前缀
            applySuffix(sqlBuffer, trimmedUppercaseSql);//处理后缀
          }
          delegate.appendSql(sqlBuffer.toString());
        }
    
        @Override
        public Map<String, Object> getBindings() {
          return delegate.getBindings();
        }
    
        @Override
        public void bind(String name, Object value) {
          delegate.bind(name, value);
        }
    
        @Override
        public int getUniqueNumber() {
          return delegate.getUniqueNumber();
        }
    
        @Override
        public void appendSql(String sql) {
          sqlBuffer.append(sql);
        }
    
        @Override
        public String getSql() {
          return delegate.getSql();
        }
    
        private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
          if (!prefixApplied) {//只处理一次
            prefixApplied = true;
            if (prefixesToOverride != null) {//如果命中词列表非空
              for (String toRemove : prefixesToOverride) {//轮询命中词列表
                if (trimmedUppercaseSql.startsWith(toRemove)) {
                  sql.delete(0, toRemove.trim().length());//如果句首命中,则删除该句首的命中词
                  break;//最多删除一次,不会把所有命中词都匹配一遍
                }
              }
            }
            if (prefix != null) {//加上前缀。删除原句首,再添上新句首,相当于句首的替换。
              sql.insert(0, " ");
              sql.insert(0, prefix);
            }
          }
        }
    
        private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
          if (!suffixApplied) {//只处理一次
            suffixApplied = true;
            if (suffixesToOverride != null) {//如果命中词列表非空
              for (String toRemove : suffixesToOverride) {//轮询命中词列表
                if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
                  int start = sql.length() - toRemove.trim().length();
                  int end = sql.length();
                  sql.delete(start, end);//如果句尾命中,则删除该句尾的命中词
                  break;//最多删除一次,不会把所有命中词都匹配一遍
                }
              }
            }
            if (suffix != null) {//加上后缀。删除原句尾,再添上新句尾,相当于句尾的替换。
              sql.append(" ");
              sql.append(suffix);
            }
          }
        }
    
      }
    
    }
  • 相关阅读:
    Java怎样对一个属性设置set或get方法的快捷键
    小程序怎样控制rich-text中的<img>标签自适应
    Java中Arrys数组常用的方法
    Java 怎样实现调用其他方法
    Java保留两位小数
    解决ajax请求跨域
    rand(7) 到rand(10)
    c++生成随机数
    批量该文件名
    正则表达式(=)
  • 原文地址:https://www.cnblogs.com/waterystone/p/7070836.html
Copyright © 2011-2022 走看看