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_";

  // &#x8868;&#x8FBE;&#x5F0F;&#x503C;&#x83B7;&#x53D6;&#x5668;
  private final ExpressionEvaluator evaluator;
  // collection userIdList
  private final String collectionExpression;
  private final SqlNode contents;
  // open&#x503C; (
  private final String open;
  // close&#x503C; )
  private final String close;
  // separator&#x5206;&#x9694;&#x7B26;&#x503C; ,
  private final String separator;
  // item&#x503C; userId
  private final String item;
  // index&#x503C; null
  private final String index;
  // mybatis&#x914D;&#x7F6E;&#x4FE1;&#x606F;
  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();
    // &#x8FED;&#x4EE3;&#x5668;
    final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
    if (!iterable.iterator().hasNext()) {
      return true;
    }
    boolean first = true;
    // &#x6DFB;&#x52A0;open&#x503C;
    applyOpen(context);
    int i = 0;
    for (Object o : iterable) {
      DynamicContext oldContext = context;
      // &#x7B2C;&#x4E00;&#x6B21;&#x5FAA;&#x73AF;first&#x4E3A;true&#xFF0C;&#x4F7F;&#x7528;new PrefixedContext(context, "")&#x6784;&#x5EFA;context&#xFF0C;&#x56E0;&#x4E3A;&#x7B2C;&#x4E00;&#x4E2A;&#x5143;&#x7D20;&#x4E4B;&#x524D;&#x4E0D;&#x7528;&#x6DFB;&#x52A0;&#x5206;&#x9694;&#x7B26;
      // &#x7B2C;&#x4E00;&#x6B21;&#x5FAA;&#x73AF;&#x5B8C;&#x6BD5;&#x540E;first&#x4E3A;false&#xFF0C;&#x4F7F;&#x7528;new PrefixedContext(context, separator)&#x6784;&#x5EFA;&#xFF0C;&#x4E4B;&#x540E;&#x5148;&#x6DFB;&#x52A0;&#x5206;&#x9694;&#x7B26;&#x518D;&#x6DFB;&#x52A0;sql&#x503C;
      if (first || separator == null) {
        context = new PrefixedContext(context, "");
      } else {
        context = new PrefixedContext(context, separator);
      }
      // &#x6BCF;&#x6B21;&#x5FAA;&#x73AF;&#x4E0D;&#x540C;&#x503C;
      int uniqueNumber = context.getUniqueNumber();
      // Issue #709
      if (o instanceof Map.Entry) {
        // map&#x7684;index&#x4E3A;key&#xFF0C;item&#x4E3A;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&#x7684;index&#x4E3A;&#x5E8F;&#x53F7;&#xFF08;&#x4ECE;0&#x5F00;&#x59CB;&#x9012;&#x589E;&#xFF09;&#xFF0C;item&#x4E3A;value&#x5143;&#x7D20;&#x503C;
        // &#x6DFB;&#x52A0;&#x5230;&#x4E0A;&#x4E0B;&#x6587;&#x7684;bindings&#x8FD9;&#x4E2A;map&#x4E2D;
        // index&#x4E0D;&#x4E3A;&#x7A7A;,key = __frch_index_uniqueNumber&#x7684;&#x683C;&#x5F0F;&#xFF0C;value = i
        applyIndex(context, i, uniqueNumber);
        // item&#x4E0D;&#x4E3A;&#x7A7A;, key = __frch_item_uniqueNumber&#x7684;&#x683C;&#x5F0F;, value = o
        applyItem(context, o, uniqueNumber);
      }

      // context -> PrefixedContext
      // &#x5904;&#x7406;sql&#x5185;&#x5BB9;
      contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
      if (first) {
        // &#x662F;&#x5426;&#x5E94;&#x7528;&#x4E86;&#x5206;&#x9694;&#x7B26;,first=false
        first = !((PrefixedContext) context).isPrefixApplied();
      }
      context = oldContext;
      i++;
    }
    // &#x6DFB;&#x52A0;close
    applyClose(context);
    // &#x79FB;&#x9664;item&#x548C;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;
  }

  // &#x52A8;&#x6001;&#x8FC7;&#x6EE4;
  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&#x5E8F;&#x53F7;
      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) {
      // &#x83B7;&#x53D6; #{}&#x5185;&#x7684;&#x5185;&#x5BB9;&#xFF0C;&#x4E4B;&#x540E;&#x7528;replaceFirst&#x5C06;item&#x66FF;&#x6362;&#x4E3A; __frch__item_0
      // &#x7C7B;&#x4F3C; #{orderId} -> #{__frch_orderId_0}, #{__frch_orderId_1}, #{__frch_orderId_2} &#x957F;&#x5EA6;&#x53D6;&#x51B3;&#x4E8E;&#x96C6;&#x5408;&#x5217;&#x8868;
      GenericTokenParser parser = new GenericTokenParser("#{", "}", content -> {
        // &#x5F00;&#x5934;&#x7A7A;&#x683C; + item + &#x540E;&#x9762;&#x662F;.,:&#x6216;&#x7A7A;&#x683C;&#x7684;&#x5B57;&#x7B26;&#x4E32;
        String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
        // itemIndex&#x4E0D;&#x4E3A;&#x7A7A;&#x4E14;&#x539F;&#x5B57;&#x7B26;&#x4E32;&#x548C;&#x65B0;&#x5B57;&#x7B26;&#x4E32;&#x76F8;&#x540C;
        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();
    }

  }

  // &#x524D;&#x7F00;&#x586B;&#x5145;&#x529F;&#x80FD;
  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) {
        // &#x6DFB;&#x52A0;&#x5206;&#x9694;&#x7B26;&#x524D;&#x7F00;&#xFF0C;&#x53EF;&#x4EE5;&#x662F;&#x9017;&#x53F7;,&#x7B49;&#x503C;
        delegate.appendSql(prefix);
        prefixApplied = true;
      }
      // &#x518D;&#x6DFB;&#x52A0;sql&#x5185;&#x5BB9;
      delegate.appendSql(sql);
    }

    @Override
    public String getSql() {
      return delegate.getSql();
    }

    @Override
    public int getUniqueNumber() {
      return delegate.getUniqueNumber();
    }
  }

}</string,></string,></object,></object,></string,>

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开头的

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) {
    // &#x5F85;&#x5904;&#x7406;&#x7684;sql&#x8282;&#x70B9;
    this.contents = contents;
    // &#x6DFB;&#x52A0;&#x524D;&#x7F00;
    this.prefix = prefix;
    // &#x8981;&#x53BB;&#x9664;&#x7684;&#x524D;&#x7F00;
    this.prefixesToOverride = prefixesToOverride;
    // &#x6DFB;&#x52A0;&#x540E;&#x7F00;
    this.suffix = suffix;
    // &#x8981;&#x53BB;&#x9664;&#x7684;&#x540E;&#x7F00;
    this.suffixesToOverride = suffixesToOverride;
    // mybatis&#x914D;&#x7F6E;
    this.configuration = configuration;
  }

  @Override
  public boolean apply(DynamicContext context) {
    FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
    // &#x5904;&#x7406;&#x8282;&#x70B9; &#x6587;&#x672C;&#x6DFB;&#x52A0;&#x5230;sqlBuffer&#x4E2D;
    boolean result = contents.apply(filteredDynamicContext);
    filteredDynamicContext.applyAll();
    return result;
  }

  // &#x89E3;&#x6790; prefixOverrides&#x548C;suffixOverrides&#x591A;&#x4E2A;&#x53EF;&#x7528;|&#x5206;&#x5272;
  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);
      // &#x59D4;&#x6258;&#x539F;&#x59CB;&#x7684;context
      this.delegate = delegate;
      this.prefixApplied = false;
      this.suffixApplied = false;
      this.sqlBuffer = new StringBuilder();
    }

    public void applyAll() {
      // &#x53BB;&#x9664;&#x524D;&#x540E;&#x7A7A;&#x683C;
      sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
      // &#x8F6C;&#x5927;&#x5199;&#x683C;&#x5F0F; &#x4E3A;&#x4E86;&#x540E;&#x7EED;&#x7684;&#x5339;&#x914D;
      String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
      if (trimmedUppercaseSql.length() > 0) {
        // &#x5904;&#x7406;&#x524D;&#x7F00;
        applyPrefix(sqlBuffer, trimmedUppercaseSql);
        // &#x5904;&#x7406;&#x540E;&#x7F00;
        applySuffix(sqlBuffer, trimmedUppercaseSql);
      }
      // &#x60F3;&#x4E0A;&#x4E0B;&#x6587;&#x8FFD;&#x52A0;sql&#x5185;&#x5BB9;
      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) {
            // &#x5224;&#x65AD;&#x5F00;&#x5934;&#x662F;&#x5426;&#x5339;&#x914D;&#xFF0C;&#x53EA;&#x53EF;&#x5339;&#x914D;&#x4E00;&#x6B21;&#x540E;&#x7EED;&#x4F1A;&#x76F4;&#x63A5;&#x9000;&#x51FA;&#x5FAA;&#x73AF;
            if (trimmedUppercaseSql.startsWith(toRemove)) {
              // &#x4ECE;&#x5934;&#x5F00;&#x59CB;&#x5220;&#x9664;&#x5339;&#x914D;&#x7684;&#x5B57;&#x7B26;toRemove&#x957F;&#x5EA6;
              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) {
            // &#x5339;&#x914D;&#x672B;&#x5C3E;
            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);
        }
      }
    }

  }

}</string,></string></string></string></string></string></string>

另外其实

<set>
&#x53BB;&#x9664;&#x9996;&#x5C3E;&#x7684;&#x9017;&#x53F7;, &#x6DFB;&#x52A0;&#x524D;&#x7F00;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>
&#x53BB;&#x9664;&#x5F00;&#x5934;&#x7684;AND/OR&#x503C;&#xFF0C;&#x6DFB;&#x52A0;&#x524D;&#x7F00;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);
  }

}</string></where></string></set>

Original: https://www.cnblogs.com/monianxd/p/16469503.html
Author: 默念x
Title: Mybatis SqlNode源码解析

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/620888/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球