MybatisSqlNode源码解析

博客 分享
0 166
优雅殿下
优雅殿下 2022-07-12 12:01:41
悬赏:0 积分 收藏

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>去除首尾的逗号, 添加前缀SETpublic 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值,添加前缀WHEREpublic 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);  }}

 

posted @ 2022-07-12 11:36 默念x 阅读(0) 评论(0) 编辑 收藏 举报
回帖
    优雅殿下

    优雅殿下 (王者 段位)

    2018 积分 (2)粉丝 (47)源码

    小小码农,大大世界

     

    温馨提示

    亦奇源码

    最新会员