Mybatis parses SQL source code analysis two

Mybatis parses SQL source code analysis two

TSMYK Java technology programming

Source code analysis of the resultMap node in the Mybatis Mapper.xml configuration file
Mybatis analysis SQL source code analysis-
Mybatis Mapper interface source code analysis
Mybatis database connection pool source code analysis
Mybatis type conversion source code analysis
Mybatis analysis configuration file source code analysis

Preface

In the last two articles Mybatis parsing SQL source code analysis 1 and the source code analysis of the resultMap node in the Mybatis Mapper.xml configuration file, I analyzed how Mybatis parses the Mapper.xml configuration file. The SQL node configured in the configuration file is parsed into one A MappedStatement object is placed in the global configuration object Configuration, where the SQL statement will be parsed into a SqlSource object. This step is carried out when Mybatis is loaded; and at runtime, how does Mybatis pass the SqlSource object to us Parsed into a complete SQL statement that can be executed by the database? Let's take a look at the source code of this part and see how Mybatis is parsed.

The analysis of this part will involve the application of combined patterns and OGNL expressions

SqlSource

In the article Mybatis parsing SQL source code analysis, we know that the SQL statements in the configuration file will be parsed into SqlSource objects, and the dynamic SQL nodes defined in the SQL statements, such as <where>, <if>, are related to SqlNode Implementation class to represent.

Now let’s take a look at the definition of the SqlSource interface:


1public interface SqlSource {
2
3  BoundSql getBoundSql(Object parameterObject);
4
5}

It has only one method getBoundSql(), the return value of this method is a BoundSql object, which contains SQL statements of? Placeholders and bound actual parameters, and we will analyze this class later.

The SqlSource interface has 4 implementation classes:
Mybatis parses SQL source code analysis two

Among them, DynamicSqlSource is responsible for processing dynamic SQL statements, and RawSqlSource is responsible for processing static SQL statements. They will eventually encapsulate StaticSqlSource and return the processed SQL. May the SQL contained in StaticSqlSource contain? Placeholders can be directly executed by the database, and the SQL in DynamicSqlSource needs further analysis before it can be executed by the database.

Let's look at these categories later, now let's look at the analysis of dynamic SQL nodes.

DynamicContext

DynamicContext This class is mainly used to store SQL statement fragments generated by parsing dynamic SQL statements. For example, when parsing the <if> tag, keywords such as and and or may be added in front of it. It is used to store these SQL fragments.


 1public class DynamicContext {
 2
 3  // 有的SQL直接使用了该字面值,如 #{_parameter}
 4  public static final String PARAMETER_OBJECT_KEY = "_parameter";
 5  // 数据库ID,可以忽略
 6  public static final String DATABASE_ID_KEY = "_databaseId";
 7  // ................
 8  // 运行时传入的参数,是一个 map,内部类
 9  private final ContextMap bindings;
10
11  // 重要,用来拼接 SQL 语句的,每解析完一个动态SQL标签的时候,会把SQL片段拼接到该属性中,最后形成完整的SQL
12  private final StringBuilder sqlBuilder = new StringBuilder();
13
14  // 构造方法,就是把传进行的参数封装为 map
15  public DynamicContext(Configuration configuration, Object parameterObject) {
16    if (parameterObject != null && !(parameterObject instanceof Map)) {
17      MetaObject metaObject = configuration.newMetaObject(parameterObject);
18      bindings = new ContextMap(metaObject);
19    } else {
20      bindings = new ContextMap(null);
21    }
22    bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
23    bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
24  }
25  // .............
26
27  // 添加 SQL 
28  public void appendSql(String sql) {
29    sqlBuilder.append(sql);
30    sqlBuilder.append(" ");
31  }
32
33  // 返回 SQL
34  public String getSql() {
35    return sqlBuilder.toString().trim();
36  }
37
38  // 存放参数的 map,继承了 HashMap,重写 get
39  static class ContextMap extends HashMap<String, Object> {
40
41    private MetaObject parameterMetaObject;
42
43    public ContextMap(MetaObject parameterMetaObject) {
44      this.parameterMetaObject = parameterMetaObject;
45    }
46
47    @Override
48    public Object get(Object key) {
49      String strKey = (String) key;
50      if (super.containsKey(strKey)) {
51        return super.get(strKey);
52      }
53      // 从运行参数中查找对应的属性
54      if (parameterMetaObject != null) {
55        return parameterMetaObject.getValue(strKey);
56      }
57      return null;
58    }
59  }
60}

SqlNode

When parsing the configuration file, you know the dynamic SQL nodes defined in the SQL statement, such as <where>, <if>, and <foreach>, which are represented by SqlNode related implementation classes. The definition of SqlNode is as follows:


1public interface SqlNode {
2  boolean apply(DynamicContext context);
3}

It has only one method, apply(context) method, which will parse the dynamic SQL node represented by the SqlNode according to the parameters passed in, and call the context.appendSql() method to append the parsed SQL fragment to the sqlBuilder property for storage. After all SqlNodes under the SQL node have been parsed, you can call context.getSql() to get a complete SQL.

Now think about how many dynamic SQL nodes Mybatis has, such as <where>, <if>, <set>, <foreach>, <choose>, etc., and there are probably as many corresponding implementation classes of SqlNode.

The implementation class of SqlNode has 10 implementation classes, corresponding to its dynamic SQL nodes:
Mybatis parses SQL source code analysis two

Next, take a look at how each dynamic SQL node is parsed.

StaticTextSqlNode

StaticTextSqlNode represents a static text SQL node, this kind of node does not need to be parsed, just add the corresponding SQL statement directly to the DynamicContext.sqlBuilder property.


 1public class StaticTextSqlNode implements SqlNode {
 2  // 对应的SQL片段
 3  private String text;
 4  public StaticTextSqlNode(String text) {
 5    this.text = text;
 6  }
 7  // 直接把 SQL 片段添加到 sqlBuilder 属性中即可
 8  @Override
 9  public boolean apply(DynamicContext context) {
10    context.appendSql(text);
11    return true;
12  }
13}

MixedSqlNode

MixedSqlNode indicates that there are multiple SqlNode nodes, and the apply() method calls the apply() method of the corresponding SqlNode node in turn:


 1public class MixedSqlNode implements SqlNode {
 2  private List<SqlNode> contents;
 3  public MixedSqlNode(List<SqlNode> contents) {
 4    this.contents = contents;
 5  }
 6  // 依次调用每个 SqlNode 的 apply 方法添加 SQL 片段
 7  @Override
 8  public boolean apply(DynamicContext context) {
 9    for (SqlNode sqlNode : contents) {
10      sqlNode.apply(context);
11    }
12    return true;
13  }
14}

TextSqlNode

TextSqlNode represents a dynamic SQL statement containing ${} placeholders. It will call the GenericTokenParser tool class to parse the ${} placeholders. For the GenericTokenParser tool class, please refer to the source code analysis of the Mybatis parsing configuration file

For example, if a piece of SQL is name=${name} and the parameter is name=zhangsan, the SQL fragment parsed by TextSqlNode is name=zhangsan, and the SQL fragment is added to the DynamicContext. The source code is as follows:


 1public class TextSqlNode implements SqlNode {
 2  // 要解析的动态SQL
 3  private String text;
 4  private Pattern injectionFilter;
 5  public TextSqlNode(String text, Pattern injectionFilter) {
 6    this.text = text;
 7    this.injectionFilter = injectionFilter;
 8  }
 9  // 解析SQL 
10  @Override
11  public boolean apply(DynamicContext context) {
12    GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
13    // 实际上调用 BindingTokenParser 的handleToken方法进行解析
14    context.appendSql(parser.parse(text));
15    return true;
16  }
17  // 添加 ${ }
18  private GenericTokenParser createParser(TokenHandler handler) {
19    return new GenericTokenParser("${", "}", handler);
20  }
21
22  private static class BindingTokenParser implements TokenHandler {
23
24    private DynamicContext context;
25    private Pattern injectionFilter;
26
27    public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
28      this.context = context;
29      this.injectionFilter = injectionFilter;
30    }
31   // 解析 ${}
32    @Override
33    public String handleToken(String content) {
34      // 获取参数
35      Object parameter = context.getBindings().get("_parameter");
36      if (parameter == null) {
37        context.getBindings().put("value", null);
38      } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
39        context.getBindings().put("value", parameter);
40      }
41      // 获取值
42      Object value = OgnlCache.getValue(content, context.getBindings());
43      String srtValue = (value == null ? "" : String.valueOf(value)); 
44      checkInjection(srtValue); // 校验合法性
45      return srtValue;
46    }
47  }
48  // 判断是否是动态SQL
49  public boolean isDynamic() {
50    DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
51    GenericTokenParser parser = createParser(checker);
52    parser.parse(text);
53    return checker.isDynamic();
54  }
55  private static class DynamicCheckerTokenParser implements TokenHandler {
56    private boolean isDynamic;
57    public DynamicCheckerTokenParser() {
58    }
59    public boolean isDynamic() {
60      return isDynamic;
61    }
62     // 调用该类就是动态SQL??
63    @Override
64    public String handleToken(String content) {
65      this.isDynamic = true;
66      return null;
67    }
68  }
69}

IfSqlNode

IfSqlNode is used to parse the <if> tag, let's take a look at the usage of the <if> tag:


1 <if test="username != null">
2    username=#{username}
3</if>

The analysis is as follows:


 1public class IfSqlNode implements SqlNode {
 2  // 用来判断 test 条件true|false的,可以忽略不看
 3  private ExpressionEvaluator evaluator;
 4  // test 表达式
 5  private String test;
 6  // <if> 的子节点
 7  private SqlNode contents;
 8
 9  public IfSqlNode(SqlNode contents, String test) {
10    this.test = test;
11    this.contents = contents;
12    this.evaluator = new ExpressionEvaluator();
13  }
14
15  @Override
16  public boolean apply(DynamicContext context) {
17    // 如果 test 表达式为true,才会执行解析SQL
18    if (evaluator.evaluateBoolean(test, context.getBindings())) {
19      contents.apply(context);
20      return true;
21    }
22    return false;
23  }
24}

TrimSqlNode

TrimSqlNode is used to parse the <trim> node, it will add or delete the corresponding prefix and suffix according to the analysis result of the child node.

Let's first look at the usage scenario of the <trim> node, if there is the following SQL:


 1<select id="queryUser" resultType="User">
 2  SELECT * FROM user
 3  WHERE 
 4  <if test="name!= null">
 5    name= #{name}
 6  </if> 
 7  <if test="address!= null">
 8    AND address like #{address}
 9  </if>
10</select>

If the conditions are not met, or only the address conditions are met, the parsed SQL is SELECT FROM user WHERE or SELECT FOMR user WHERE AND address ...

You can use the <where> tag to solve this problem. The <where> tag will only insert the WHERE clause when there is at least one child element that returns the SQL clause. Moreover, if the beginning of the statement is AND or OR, the where element will also remove them.

In addition, we can also use <trim> instead, as shown below:


1<trim prefix="WHERE" prefixOverrides="AND |OR ">
2  ... 
3</trim>

Its function is to remove all the content specified in the prefixOverrides attribute, and insert the content specified in the prefix attribute


 1public class TrimSqlNode implements SqlNode {
 2  // trim 的子节点
 3  private SqlNode contents;
 4  //为 <trim> 节点包含的SQL添加的前缀字符串
 5  private String prefix;
 6  //为 <trim> 节点包含的SQL添加的后缀字符串
 7  private String suffix;
 8  // 删除指定的前缀
 9  private List<String> prefixesToOverride;
10  // 删除指定的后缀
11  private List<String> suffixesToOverride;
12  private Configuration configuration;
13
14  // 构造方法,同时解析删除的前缀和后缀字符串
15  public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
16    this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
17  }
18  // 解析删除的前缀和后缀字符串
19  private static List<String> parseOverrides(String overrides) {
20    if (overrides != null) {
21      // 按 | 分割,放到集合中
22      final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
23      final List<String> list = new ArrayList<String>(parser.countTokens());
24      while (parser.hasMoreTokens()) {
25        list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
26      }
27      return list;
28    }
29
30  /...........
31  @Override
32  public boolean apply(DynamicContext context) {
33    FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
34    // 解析子节点
35    boolean result = contents.apply(filteredDynamicContext);
36    // 处理前缀和后缀
37    filteredDynamicContext.applyAll();
38    return result;
39  }
40    // 内部类
41  private class FilteredDynamicContext extends DynamicContext {
42    private DynamicContext delegate;
43    // 是否已经处理过前缀,默认为false
44    private boolean prefixApplied;
45    // 是否已经处理过后缀,默认为false
46    private boolean suffixApplied;
47     // SQL
48    private StringBuilder sqlBuffer;
49    // 
50    public void applyAll() {
51      // 获取子节点解析结果
52      sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
53      String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
54      if (trimmedUppercaseSql.length() > 0) {
55        // 处理前缀
56        applyPrefix(sqlBuffer, trimmedUppercaseSql);
57        // 处理后缀
58        applySuffix(sqlBuffer, trimmedUppercaseSql);
59      }
60      // 最后拼接SQL
61      delegate.appendSql(sqlBuffer.toString());
62    }
63   // 处理前缀,处理后缀同理
64   private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
65      if (!prefixApplied) {
66        // 如果还没处理过,则处理
67        prefixApplied = true;
68        if (prefixesToOverride != null) {
69          for (String toRemove : prefixesToOverride) {
70            // 删除指定前缀
71            if (trimmedUppercaseSql.startsWith(toRemove)) {
72              sql.delete(0, toRemove.trim().length());
73              break;
74            }
75          }
76        }
77         // 添加指定前缀
78        if (prefix != null) {
79          sql.insert(0, " ");
80          sql.insert(0, prefix);
81        }
82      }
83    }
84}

WhereSqlNode

WhereSqlNode is used to process the <where> tag. As mentioned in the introduction of TrimSqlNode, the <where> tag will automatically be prefixed with where, and the “and” will be removed. In fact, the <where> tag is parsed using the WhereSqlNode class, while WhereSqlNode is The subclass of TrimSqlNode is just that the prefix attribute of the trim tag is set to where, and prefixToOverride is set to AND | OR.


1public class WhereSqlNode extends TrimSqlNode {
2
3  private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");
4
5  public WhereSqlNode(Configuration configuration, SqlNode contents) {
6    super(configuration, contents, "WHERE", prefixList, null, null);
7  }
8}

You can see that the prefix is ​​where, the prefix to be deleted is AND | OR, and the suffix and the suffix to be deleted are null.

SetSqlNode

SetSqlNode is mainly used to parse the <set> tag. Like the parsing class of the <where> tag, it also inherits the TrimSqlNode class, except that the prefixes that need to be added and the suffixes that need to be deleted are set to SET and commas.


1public class SetSqlNode extends TrimSqlNode {
2
3  private static List<String> suffixList = Arrays.asList(",");
4
5  public SetSqlNode(Configuration configuration,SqlNode contents) {
6    super(configuration, contents, "SET", null, null, suffixList);
7
8}

So when using the <set> tag, if the last condition is not met, the last comma converted to SQL will be automatically removed, as shown below:


1<update id="updateAuthorIfNecessary">
2  update Author
3    <set>
4      <if test="username != null">username=#{username},</if>
5      <if test="password != null">password=#{password},</if>
6      <if test="bio != null">bio=#{bio}</if>
7    </set>
8  where id=#{id}
9</update>

If the bio condition is not met, the last comma will not affect the execution of SQL, it should be automatically removed.

ForeachSqlNode

ForeachSqlNode is mainly used to parse the <foreach> node, let's take a look at the usage of the <foreach> node:


1<select id="queryUsers" resultType="User">
2  SELECT * FROM user WHERE ID in
3  <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
4        #{item}
5  </foreach>
6</select>

Source code:
Let's take a look at its two internal classes:

PrefixedContext


 1  private class PrefixedContext extends DynamicContext {
 2
 3    private DynamicContext delegate;
 4    // 指定的前缀
 5    private String prefix;
 6    // 是否处理过前缀
 7    private boolean prefixApplied;
 8
 9    // .......
10
11    @Override
12    public void appendSql(String sql) {
13      // 如果还没有处理前缀,则添加前缀
14      if (!prefixApplied && sql != null && sql.trim().length() > 0) {
15        delegate.appendSql(prefix);
16        prefixApplied = true;
17      }
18       // 拼接SQL
19      delegate.appendSql(sql);
20    }
21}

FilteredDynamicContext

FilteredDynamicContext is used to process #{} placeholders, but no parameters are bound, but #{item} is converted to #{_frch_item_1} and other placeholders.


 1  private static class FilteredDynamicContext extends DynamicContext {
 2    private DynamicContext delegate;
 3    //对应集合项在集合的索引位置
 4    private int index;
 5    // item的索引
 6    private String itemIndex;
 7    // item的值
 8    private String item;
 9    //.............
10    // 解析 #{item}
11    @Override
12    public void appendSql(String sql) {
13      GenericTokenParser parser = new GenericTokenParser("#{", "}", new TokenHandler() {
14        @Override
15        public String handleToken(String content) {
16          // 把 #{itm} 转换为 #{__frch_item_1} 之类的
17          String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
18           // 把 #{itmIndex} 转换为 #{__frch_itemIndex_1} 之类的
19          if (itemIndex != null && newContent.equals(content)) {
20            newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index));
21          }
22          // 再返回 #{__frch_item_1} 或 #{__frch_itemIndex_1}
23          return new StringBuilder("#{").append(newContent).append("}").toString();
24        }
25      });
26      // 拼接SQL
27      delegate.appendSql(parser.parse(sql));
28    }
29  private static String itemizeItem(String item, int i) {
30    return new StringBuilder("__frch_").append(item).append("_").append(i).toString();
31  }
32}

ForeachSqlNode

After understanding the two internal classes of ForeachSqlNode, let's take a look at it:


 1public class ForEachSqlNode implements SqlNode {
 2  public static final String ITEM_PREFIX = "__frch_";
 3  // 判断循环的终止条件
 4  private ExpressionEvaluator evaluator;
 5  // 循环的集合
 6  private String collectionExpression;
 7  // 子节点
 8  private SqlNode contents;
 9  // 开始字符
10  private String open;
11  // 结束字符
12  private String close;
13  // 分隔符
14  private String separator;
15  // 本次循环的元素,如果集合为 map,则index 为key,item为value
16  private String item;
17  // 本次循环的次数
18  private String index;
19  private Configuration configuration;
20
21  // ...............
22
23  @Override
24  public boolean apply(DynamicContext context) {
25    // 获取参数
26    Map<String, Object> bindings = context.getBindings();
27    final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
28    if (!iterable.iterator().hasNext()) {
29      return true;
30    }
31    boolean first = true;
32    // 添加开始字符串
33    applyOpen(context);
34    int i = 0;
35    for (Object o : iterable) {
36      DynamicContext oldContext = context;
37      if (first) {
38        // 如果是集合的第一项,则前缀prefix为空字符串
39        context = new PrefixedContext(context, "");
40      } else if (separator != null) {
41        // 如果分隔符不为空,则指定分隔符
42        context = new PrefixedContext(context, separator);
43      } else {
44          // 不指定分隔符,在默认为空
45          context = new PrefixedContext(context, "");
46      }
47      int uniqueNumber = context.getUniqueNumber();  
48      if (o instanceof Map.Entry) {
49        // 如果集合是map类型,则将集合中的key和value添加到bindings参数集合中保存
50        Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
51        // 所以循环的集合为map类型,则index为key,item为value,就是在这里设置的
52        applyIndex(context, mapEntry.getKey(), uniqueNumber);
53        applyItem(context, mapEntry.getValue(), uniqueNumber);
54      } else {
55        // 不是map类型,则将集合中元素的索引和元素添加到 bindings集合中
56        applyIndex(context, i, uniqueNumber);
57        applyItem(context, o, uniqueNumber);
58      }
59      // 调用 FilteredDynamicContext 的apply方法进行处理
60      contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
61      if (first) {
62        first = !((PrefixedContext) context).isPrefixApplied();
63      }
64      context = oldContext;
65      i++;
66    }
67     // 添加结束字符串
68    applyClose(context);
69    return true;
70  }
71
72  private void applyIndex(DynamicContext context, Object o, int i) {
73    if (index != null) {
74      context.bind(index, o); // key为idnex,value为集合元素
75      context.bind(itemizeItem(index, i), o); // 为index添加前缀和后缀形成新的key
76    }
77  }
78
79  private void applyItem(DynamicContext context, Object o, int i) {
80    if (item != null) {
81      context.bind(item, o);
82      context.bind(itemizeItem(item, i), o);
83    }
84  }
85}

In the beginning example:


1<select id="queryUsers" resultType="User">
2  SELECT * FROM user WHERE ID in
3  <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
4        #{item}
5  </foreach>
6</select>

The parsed SQL is as follows: SELECT * FORM user WHERE ID in (#{ frch_item_0}, #{ frch_item_1})

ChooseSqlNode

ChooseSqlNode is used to parse the <choose> node, which is relatively simple:


 1public class ChooseSqlNode implements SqlNode {
 2  // 对应 <otherwise> 节点
 3  private SqlNode defaultSqlNode;
 4  // 对应<when> 节点
 5  private List<SqlNode> ifSqlNodes;
 6
 7  public ChooseSqlNode(List<SqlNode> ifSqlNodes, SqlNode defaultSqlNode) {
 8    this.ifSqlNodes = ifSqlNodes;
 9    this.defaultSqlNode = defaultSqlNode;
10  }
11
12  @Override
13  public boolean apply(DynamicContext context) {
14    for (SqlNode sqlNode : ifSqlNodes) {
15      if (sqlNode.apply(context)) {
16        return true;
17      }
18    }
19    if (defaultSqlNode != null) {
20      defaultSqlNode.apply(context);
21      return true;
22    }
23    return false;
24  }
25}

SqlSourceBuilder

After the SQL node is parsed by each SqlNode.apply(), the SQL statement will be passed to SqlSourceBuilder for further analysis. SqlSourceBuilder mainly completes two parts: one is to parse the attributes in the #{} placeholder, the format is similar to #{__frc_item_0, javaType=int, jdbcType=number, typeHandler=MyTypeHander}, and the other is to replace #{} in SQL with ?.


 1public class SqlSourceBuilder extends BaseBuilder {
 2  // 参数属性
 3  private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
 4
 5  public SqlSourceBuilder(Configuration configuration) {
 6    super(configuration);
 7  }
 8  // 解析SQL
 9  // originalSql 经过 SqlNode.apply() 解析后的SQL
10  // parameterType 传入的参数类型
11  // additionalParameters 形参和实参的对应关系,即 DynamicContex.bindings 参数集合
12  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
13    // ParameterMappingTokenHandler 解析 #{}
14    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
15    // GenericTokenParser 与ParameterMappingTokenHandler 配合解析 #{}
16    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
17    // 得到含有 ? 占位符的SQL,
18    String sql = parser.parse(originalSql);
19    // 根据含有?占位符的SQL和参数创建StaticSqlSource对象
20    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
21  }
22  // 内部类,用来解析 #{}
23  private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
24    // parameterMappings 记录了 #{} 中的属性,可以忽略
25    private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
26    private Class<?> parameterType;
27    private MetaObject metaParameters;
28
29    public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
30      super(configuration);
31      this.parameterType = parameterType;
32      this.metaParameters = configuration.newMetaObject(additionalParameters);
33    }
34
35    public List<ParameterMapping> getParameterMappings() {
36      return parameterMappings;
37    }
38
39    @Override
40    public String handleToken(String content) {
41      parameterMappings.add(buildParameterMapping(content));
42      // 替换 ? 占位符
43      return "?";
44    }
45}

After parsing by the above SqlSourceBuilder, a StaticSqlSource object is obtained:

StaticSqlSource


 1public class StaticSqlSource implements SqlSource {
 2  // SQL
 3  private String sql;
 4  // 参数的属性集合
 5  private List<ParameterMapping> parameterMappings;
 6  private Configuration configuration;
 7
 8  public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
 9    this.sql = sql;
10    this.parameterMappings = parameterMappings;
11    this.configuration = configuration;
12  }
13  // 直接返回 BoundSql
14  @Override
15  public BoundSql getBoundSql(Object parameterObject) {
16    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
17  }
18}

BoundSql


 1public class BoundSql {
 2  // SQL ,可能含有 ? 占位符
 3  private String sql;
 4  // 参数的属性集合
 5  private List<ParameterMapping> parameterMappings;
 6  // 传入的实际参数
 7  private Object parameterObject;
 8  // 空的hashmap,之后中复制 DynamicContext.bindings 中的内容
 9  private Map<String, Object> additionalParameters;
10  //additionalParameters集合对象的 MetaObject 对象
11  private MetaObject metaParameters;
12}

DynamicSqlSource

The last step uses DynamicSqlSource to parse dynamic SQL statements:


 1public class DynamicSqlSource implements SqlSource {
 2
 3  private Configuration configuration;
 4  private SqlNode rootSqlNode;
 5
 6  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
 7    this.configuration = configuration;
 8    this.rootSqlNode = rootSqlNode;
 9  }
10
11  @Override
12  public BoundSql getBoundSql(Object parameterObject) {
13    // 创建DynamicContext,parameterObject为传进来的参数
14    DynamicContext context = new DynamicContext(configuration, parameterObject);
15    //rootSqlNode.apply 方法会调用整个树形结构中全部的SqlNode的apply方法,每个SqlNode的apply方法解析得到的SQL片段会添加到 context中,最后调用 getSql 得到完整的SQL
16    rootSqlNode.apply(context);
17    // 解析 #{} 参数属性,并将 #{} 替换为 ?
18    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
19    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
20    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
21    // 创建 BoundSql 
22    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
23    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
24      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
25    }
26    return boundSql;
27  }
28}

RawSqlSource

In addition to DynamicSqlSource parsing dynamic SQL, there is also RawSqlSource to parsing static SQL. The principle is similar.

At this point, SQL is parsed.

Guess you like

Origin blog.51cto.com/15077536/2608632