Analysis of dynamic tag foreach parsing process in mybatis

Briefly

     For dynamic tags (foreach, if, choose, etc.) in mybatis, there will be corresponding classes to resolve. SqlNode is the top-level parsing interface, and each dynamic tag implements its apply method to complete its own parsing operation. The parsing implementation class corresponding to the foreach tag is foreachSqlNode.
     The process of foreach tag parsing is the process of parsing each attribute (collection, index, item, etc.) in the foreach tag. Each attribute has corresponding parsing logic. After the sql parsing is completed, data operations such as query or deletion are performed, and then the result set is encapsulated. This article only describes the foreach tag parsing.

Execute demo

dao interface:

List<News> findNews(@Param("ids") List<Integer> ids);

xml configuration file:

 <select id="findNews" resultType="com.it.txm.demo.News">
        select id,title from find_news
         where id in
        (
        <foreach collection="ids" item="item" separator=",">
            #{item}
        </foreach>
        )
    </select>

Test demo:

ArrayList<Integer> list1 = new ArrayList<>();
	list1.add(777);
	list1.add(797);
	List<News> list = newsMapper.findNews(list1);
	System.out.println(list);

Perform process analysis

debug debugging goes into getting BoundSql (that is, parsing sql)

     SqlNode is the top-level interface for dynamic labels with the following implementation classes.

     MixedSqlNode can be thought of as a combination of multiple dynamic tags, which cycle through each tag.Source:

public class MixedSqlNode implements SqlNode {
  private final List<SqlNode> contents;
	// A collection of implementation sqlNode implementation classes is added to MixedSqlNode when the MixedSqlNode object is created
  public MixedSqlNode(List<SqlNode> contents) {
    this.contents = contents;
  }
	// MixedSqlNode implements the apply method by executing the apply implementation logic for each tag.
  @Override
  public boolean apply(DynamicContext context) {
    contents.forEach(node -> node.apply(context));
    return true;
  }
}

     Specifically, in demo, mixedSqlNode has two implementation classes for the sqlNode interface:
StaticTextSqlNode,ForEachSqlNode;
StaticTextSqlNode implements apply by splicing sql together with the following source code:

public class StaticTextSqlNode implements SqlNode {
  private final String text;

  public StaticTextSqlNode(String text) {
    this.text = text;
  }
	// Stitching sql with dynamic context objects
  @Override
  public boolean apply(DynamicContext context) {
    context.appendSql(text);
    return true;
  }
  // Finally, sqlBuilder is used for parameter splicing
	public void appendSql(String sql) {
	    sqlBuilder.add(sql);
	  }
}

     Specifically, in the case where StaticTextSqlNode actually parses the following two lines of sql:

     The following instructions are consistent with the actual parsing execution in the source code:

     Following is ForEachSqlNode, the source code is as follows:

public boolean apply(DynamicContext context) {
	// Get mapping binding information from dynamic context (only parameter mapping information is used here.)
    Map<String, Object> bindings = context.getBindings();
    // Resolves the collection property in the foreach tag. Returns the iterator type parameter, which is actually the specific parameter value of the collection in the collection tag that is resolved: 777 797
    final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
    if (!iterable.iterator().hasNext()) {
      return true;
    }
    boolean first = true;
    // Parse the open attribute in the foreach tag. The specific parsing method is sql splicing, which is not expanded here because of its simplicity.
    applyOpen(context);
    int i = 0;
    // Traverse the arguments in the iterator.
    for (Object o : iterable) {
      DynamicContext oldContext = context{
      // The separator separator separator property in the foreach tag is handled here, and the new PrefixedContext() actually performs the setting of the split property in the DynamicContext. If it is not empty, it sets the empty string if the actual separator used in the foreach tag is empty, and a comma is used in the demo.
      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) {
        @SuppressWarnings("unchecked")
        Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
        applyIndex(context, mapEntry.getKey(), uniqueNumber);
        applyItem(context, mapEntry.getValue(), uniqueNumber);
      } else {
		// applyIndex handles the index attribute in the foreach tag, which is expanded below
        applyIndex(context, i, uniqueNumber);
        // The main purpose of applyItem is to parse and assign the item attribute in the foreach tag, which is described below
        applyItem(context, o, uniqueNumber);
      }
      // Splicing individual parameters parsed in foreach Tags
      contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
      if (first) {
        first = !((PrefixedContext) context).isPrefixApplied();
      }
      context = oldContext;
      i++;
    }
    // Performing close attribute corresponding processing in foreach tag actually performs parameter splicing.
    applyClose(context);
    context.getBindings().remove(item);
    context.getBindings().remove(index);
    return true;
  }

     Briefly speaking, DynamicContext can be understood as a context object of dynamic parameters, including method parameters in map form and sqlBuilderd for sql splicing.

     The main function of applyIndex is to process the index attribute in foreach tag, store object information in dynamic context of DynamicContext as map, and finally encapsulate number: bindings.put(name, value) is executed in context.bind;

  private void applyIndex(DynamicContext context, Object o, int i) {
  // Perform the following logic with the index property set in the foreach tag.
    if (index != null) {
    	// Assemble the map as key with the actual parameter o in the DynamicContext dynamic sql context.
      context.bind(index, o);
      // The itemizeItem action parameter is assembled, and the format after parsing in foreach defaults: u Frch_ + Item +''+ i, then saved in the DynamicContext dynamic sql context as key-value.
      context.bind(itemizeItem(index, i), o);
    }
  }

     applyItem works the same way as applyIndex, when the item property in the foreach tag is not empty: it stores object information in the DynamicContext dynamic context as a key-value.

  private void applyItem(DynamicContext context, Object o, int i) {
    if (item != null) {
      context.bind(item, o);
      context.bind(itemizeItem(item, i), o);
    }
  }

     The query sql assembled from the sql stitching object in the DynamicContext after execution is complete is as follows:

The foreach tag parsing analysis is now complete.
     Welcome to the small partner comment area to share and make progress together!

Keywords: Java Mybatis Back-end

Added by torsoboy on Tue, 07 Dec 2021 21:53:57 +0200