• Mybatis SqlNode源码解析


    1.ForEachSqlNode

    mybatis的foreach标签可以将列表、数组中的元素拼接起来,中间可以指定分隔符separator

      <select id="getByUserId" resultMap="BaseMap">
        select <include refid="BaseFields"></include>
        from user
        <where>
          user_id in
          <foreach collection="userIdList" item="userId" open="(" separator="," close=")">
            #{userId}
          </foreach>
        </where>
      </select>

    上面这段select sql代码使用了foreach标签,传入了一个userIdList的列表,首先会转化为一个ForeachSqlNode对象,经过处理后foreach标签里面的代码会解析成 (假设userIdList=[101,102,103])

    (#{__frch_userId_0}, #{__frch_userId_1},#{__frch_userId_2}) , 后续预处理值替换后就会变成 (101,102,103)

    下面是具体的ForEachSqlNode的解析过程源码:

    public class ForEachSqlNode implements SqlNode {
      public static final String ITEM_PREFIX = "__frch_";
    
      // 表达式值获取器
      private final ExpressionEvaluator evaluator;
      // collection userIdList
      private final String collectionExpression;
      private final SqlNode contents;
      // open值 (
      private final String open;
      // close值 )
      private final String close;
      // separator分隔符值 ,
      private final String separator;
      // item值 userId
      private final String item;
      // index值 null
      private final String index;
      // mybatis配置信息
      private final Configuration configuration;
    
      public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, String index, String item, String open, String close, String separator) {
        this.evaluator = new ExpressionEvaluator();
        this.collectionExpression = collectionExpression;
        this.contents = contents;
        this.open = open;
        this.close = close;
        this.separator = separator;
        this.index = index;
        this.item = item;
        this.configuration = configuration;
      }
    
      @Override
      public boolean apply(DynamicContext context) {
        Map<String, Object> bindings = context.getBindings();
        // 迭代器
        final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
        if (!iterable.iterator().hasNext()) {
          return true;
        }
        boolean first = true;
        // 添加open值
        applyOpen(context);
        int i = 0;
        for (Object o : iterable) {
          DynamicContext oldContext = context;
          // 第一次循环first为true,使用new PrefixedContext(context, "")构建context,因为第一个元素之前不用添加分隔符
          // 第一次循环完毕后first为false,使用new PrefixedContext(context, separator)构建,之后先添加分隔符再添加sql值
          if (first || separator == null) {
            context = new PrefixedContext(context, "");
          } else {
            context = new PrefixedContext(context, separator);
          }
          // 每次循环不同值
          int uniqueNumber = context.getUniqueNumber();
          // Issue #709
          if (o instanceof Map.Entry) {
            // map的index为key,item为value
            @SuppressWarnings("unchecked")
            Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
            applyIndex(context, mapEntry.getKey(), uniqueNumber);
            applyItem(context, mapEntry.getValue(), uniqueNumber);
          } else {
            // list的index为序号(从0开始递增),item为value元素值
            // 添加到上下文的bindings这个map中
            // index不为空,key = __frch_index_uniqueNumber的格式,value = i
            applyIndex(context, i, uniqueNumber);
            // item不为空, key = __frch_item_uniqueNumber的格式, value = o
            applyItem(context, o, uniqueNumber);
          }
    
          // context -> PrefixedContext
          // 处理sql内容
          contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
          if (first) {
            // 是否应用了分隔符,first=false
            first = !((PrefixedContext) context).isPrefixApplied();
          }
          context = oldContext;
          i++;
        }
        // 添加close
        applyClose(context);
        // 移除item和index
        context.getBindings().remove(item);
        context.getBindings().remove(index);
        return true;
      }
    
      private void applyIndex(DynamicContext context, Object o, int i) {
        if (index != null) {
          context.bind(index, o);
          context.bind(itemizeItem(index, i), o);
        }
      }
    
      private void applyItem(DynamicContext context, Object o, int i) {
        if (item != null) {
          context.bind(item, o);
          context.bind(itemizeItem(item, i), o);
        }
      }
    
      private void applyOpen(DynamicContext context) {
        if (open != null) {
          context.appendSql(open);
        }
      }
    
      private void applyClose(DynamicContext context) {
        if (close != null) {
          context.appendSql(close);
        }
      }
    
      private static String itemizeItem(String item, int i) {
        return ITEM_PREFIX + item + "_" + i;
      }
    
      // 动态过滤
      private static class FilteredDynamicContext extends DynamicContext {
        private final DynamicContext delegate;
        private final int index;
        private final String itemIndex;
        private final String item;
    
        public FilteredDynamicContext(Configuration configuration,DynamicContext delegate, String itemIndex, String item, int i) {
          super(configuration, null);
          this.delegate = delegate;
          // uniqueNumber序号
          this.index = i;
          this.itemIndex = itemIndex;
          this.item = item;
        }
    
        @Override
        public Map<String, Object> getBindings() {
          return delegate.getBindings();
        }
    
        @Override
        public void bind(String name, Object value) {
          delegate.bind(name, value);
        }
    
        @Override
        public String getSql() {
          return delegate.getSql();
        }
    
        @Override
        public void appendSql(String sql) {
          // 获取 #{}内的内容,之后用replaceFirst将item替换为 __frch__item_0
          // 类似 #{orderId} -> #{__frch_orderId_0}, #{__frch_orderId_1}, #{__frch_orderId_2} 长度取决于集合列表
          GenericTokenParser parser = new GenericTokenParser("#{", "}", content -> {
            // 开头空格 + item + 后面是.,:或空格的字符串
            String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
            // itemIndex不为空且原字符串和新字符串相同
            if (itemIndex != null && newContent.equals(content)) {
              newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index));
            }
            return "#{" + newContent + "}";
          });
    
          delegate.appendSql(parser.parse(sql));
        }
    
        @Override
        public int getUniqueNumber() {
          return delegate.getUniqueNumber();
        }
    
      }
    
    
      // 前缀填充功能
      private class PrefixedContext extends DynamicContext {
        private final DynamicContext delegate;
        private final String prefix;
        private boolean prefixApplied;
    
        public PrefixedContext(DynamicContext delegate, String prefix) {
          super(configuration, null);
          this.delegate = delegate;
          this.prefix = prefix;
          this.prefixApplied = false;
        }
    
        public boolean isPrefixApplied() {
          return prefixApplied;
        }
    
        @Override
        public Map<String, Object> getBindings() {
          return delegate.getBindings();
        }
    
        @Override
        public void bind(String name, Object value) {
          delegate.bind(name, value);
        }
    
        @Override
        public void appendSql(String sql) {
          if (!prefixApplied && sql != null && sql.trim().length() > 0) {
            // 添加分隔符前缀,可以是逗号,等值
            delegate.appendSql(prefix);
            prefixApplied = true;
          }
          // 再添加sql内容
          delegate.appendSql(sql);
        }
    
        @Override
        public String getSql() {
          return delegate.getSql();
        }
    
        @Override
        public int getUniqueNumber() {
          return delegate.getUniqueNumber();
        }
      }
    
    }
    折叠

     

    2.TrimSqlNode

    mybatis的trim标签可以添加/删除指定的前缀、后缀值

      <select id="getByUserId" resultMap="BaseMap">
        select <include refid="BaseFields"></include>
        from user
        <trim prefix="where" prefixOverrides="and | or">
          and user_id = #{userId}
        </trim>
      </select>

    这段代码使用了trim标签,会先匹配去除and | or开头的<trim>标签内的sql内容,接着加上前缀where,会解析成

    where user_id = #{userId}

    下面是具体的ForEachSqlNode的解析过程源码:

    public class TrimSqlNode implements SqlNode {
    
      private final SqlNode contents;
      private final String prefix;
      private final String suffix;
      private final List<String> prefixesToOverride;
      private final List<String> suffixesToOverride;
      private final 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) {
        // 待处理的sql节点
        this.contents = contents;
        // 添加前缀
        this.prefix = prefix;
        // 要去除的前缀
        this.prefixesToOverride = prefixesToOverride;
        // 添加后缀
        this.suffix = suffix;
        // 要去除的后缀
        this.suffixesToOverride = suffixesToOverride;
        // mybatis配置
        this.configuration = configuration;
      }
    
      @Override
      public boolean apply(DynamicContext context) {
        FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
        // 处理节点 文本添加到sqlBuffer中
        boolean result = contents.apply(filteredDynamicContext);
        filteredDynamicContext.applyAll();
        return result;
      }
    
      // 解析 prefixOverrides和suffixOverrides多个可用|分割
      private static List<String> parseOverrides(String overrides) {
        if (overrides != null) {
          final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
          final List<String> list = new ArrayList<>(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);
          // 委托原始的context
          this.delegate = delegate;
          this.prefixApplied = false;
          this.suffixApplied = false;
          this.sqlBuffer = new StringBuilder();
        }
    
        public void applyAll() {
          // 去除前后空格
          sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
          // 转大写格式 为了后续的匹配
          String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
          if (trimmedUppercaseSql.length() > 0) {
            // 处理前缀
            applyPrefix(sqlBuffer, trimmedUppercaseSql);
            // 处理后缀
            applySuffix(sqlBuffer, trimmedUppercaseSql);
          }
          // 想上下文追加sql内容
          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)) {
                  // 从头开始删除匹配的字符toRemove长度
                  sql.delete(0, toRemove.trim().length());
                  break;
                }
              }
            }
            if (prefix != null) {
    //          sql.insert(0, prefix + " ");
              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);
            }
          }
        }
    
      }
    
    }
    折叠

    另外其实<where>和<set>标签底层的原理也是和<trim>标签相同的,有继承关系,代码如下

    <set>
    去除首尾的逗号, 添加前缀SET
    public class SetSqlNode extends TrimSqlNode {
    
      private static final List<String> COMMA = Collections.singletonList(",");
    
      public SetSqlNode(Configuration configuration,SqlNode contents) {
        super(configuration, contents, "SET", COMMA, null, COMMA);
      }
    
    }
    
    <where>
    去除开头的AND/OR值,添加前缀WHERE
    public class WhereSqlNode extends TrimSqlNode {
    
      private static List<String> prefixList = Arrays.asList("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, null, null);
      }
    
    }
    折叠

     

  • 相关阅读:
    8月23日计算机视觉理论学习笔记——图像检索
    react 父组件调用子组件的属性或方法
    服务器如何提供性能呢?
    GH6159镍铬钴变形高温合金材料
    2024第八届杭州国际智慧城市博览会:建筑与智能,智慧与未来
    从入门到出师,关于学习RPA的建议!
    你认为低代码能够完全取代程序猿吗?
    时间序列预测(9) — Informer源码详解与运行
    QT-day2
    查找与排序
  • 原文地址:https://www.cnblogs.com/monianxd/p/16469503.html