首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Mybatis 解析 SQL 源码分析二

Mybatis 解析 SQL 源码分析二

作者头像
Java技术编程
修改于 2020-05-21 12:47:28
修改于 2020-05-21 12:47:28
1.5K0
举报
文章被收录于专栏:Java技术编程Java技术编程

本文首发于个人公众号 Java 技术大杂烩,欢迎关注

Mybatis Mapper.xml 配置文件中 resultMap 节点的源码解析

Mybatis 解析 SQL 源码分析一

Mybatis Mapper 接口源码解析

Mybatis 数据库连接池源码解析

Mybatis 类型转换源码分析

Mybatis 解析配置文件的源码解析

前言

在上两篇文章 Mybatis 解析 SQL 源码分析一 和 Mybatis Mapper.xml 配置文件中 resultMap 节点的源码解析 中分析了 Mybatis 是如何解析 Mapper.xml 配置文件的,配置文件中配置的 SQL 节点被解析成了一个个的 MappedStatement 对象放到了全局的配置对象 Configuration 中,其中 SQL 语句会被解析成 SqlSource 对象,这一步是在 Mybatis 加载的时候进行的;而在运行的时候,Mybatis 是如何把 SqlSource 对象和我们传入的参数解析成一条完整的,能够被数据库执行的 SQL 语句呢?下面就来看下这部分的源码,看看 Mybatis 是如何解析的。

该部分的解析会涉及到 组合模式 和 OGNL 表达式的应用

SqlSource

在 Mybatis 解析 SQL 源码分析一 文章中我们知道,配置文件中的 SQL 语句会被解析成 SqlSource 对象,而 SQL 语句中定义的动态 SQL 节点,如 <where>,<if> 之类的使用 SqlNode 相关的实现类来表示。

现在先来看看 SqlSource 接口的定义:

代码语言:javascript
复制
1public interface SqlSource {
2
3  BoundSql getBoundSql(Object parameterObject);
4
5}

它只有一个方法 getBoundSql() ,该方法的返回值 BoundSql对象,它包含了 ? 占位符的 SQL 语句,以及绑定的实参,后面再来分析该类。

该 SqlSource 接口一共有 4 个实现类:

其中 DynamicSqlSource 负责处理动态 SQL 语句,RawSqlSource 负责处理静态 SQL 语句,它们最终会把处理后的 SQL 封装 StaticSqlSource 进行返回,StaticSqlSource 包含的 SQL 可能含有 ? 占位符,可以被数据库直接执行,而 DynamicSqlSource 中的 SQL 还需要进一步解析才能被数据库执行。

这几个类后面再来看,现在先来看看动态 SQL 节点的解析。

DynamicContext

DynamicContext 该类主要用来存放解析动态 SQL 语句产生的 SQL 语句片段,比如说 解析 <if> 标签的时候,前面可能加 and 和 or 之类的关键字,它就是用来存放这些 SQL 片段的。

代码语言:javascript
复制
 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

在解析配置文件的时候知道 SQL 语句中定义的动态 SQL 节点,如 <where>,<if>,<foreach> 之类的使用SqlNode 相关的实现类来表示。SqlNode 的定义如下:

代码语言:javascript
复制
1public interface SqlNode {
2  boolean apply(DynamicContext context);
3}

它只有一个方法 apply(context) 方法,该方法会根据传进来的参数,解析该 SqlNode 代表的动态 SQL 节点,并调用 context.appendSql() 方法把解析后的 SQL 片段追加到 sqlBuilder 属性中进行保存。当 SQL 节点下的所有的 SqlNode 解析完毕后,就可以调用 context.getSql() 获取一条完整的 SQL。

现在来想想 Mybatis 有多少种动态SQL节点,如 <where>,<if>,<set>,<foreach>,<choose> 等等之类的,对应的 SqlNode 的实现类大概就有多少个。

SqlNode 的实现类有 10 个实现类,分别对应其动态SQL节点:

接下来依次看下每个动态的 SQL 节点是如何解析的.

StaticTextSqlNode

StaticTextSqlNode 表示的是 静态文本SQL节点,该种节点不需要解析,直接把对应的 SQL 语句添加到 DynamicContext.sqlBuilder 属性中即可。

代码语言:javascript
复制
 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 表示有多个 SqlNode节点,apply() 方法依次调用对应 SqlNode 节点的apply()方法:

代码语言:javascript
复制
 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 表示的是包含有 ${} 占位符的动态 SQL 语句,它会调用 GenericTokenParser 工具类来解析 ${} 占位符,关于 GenericTokenParser 工具类,可以参考 Mybatis 解析配置文件的源码解析

比如有段 SQL 为 name=${name},参数为 name=zhangsan,则通过 TextSqlNode 解析后的 SQL 片段为 name=zhangsan,并把该 SQL 片段添加到 DynamicContext中。源码如下:

代码语言:javascript
复制
 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 用来解析 <if> 标签的,先来看看 <if> 标签的用法:

代码语言:javascript
复制
1 <if test="username != null">
2    username=#{username}
3</if> 

解析如下:

代码语言:javascript
复制
 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 用来解析<trim>节点,它会根据子节点的解析结果添加或删除相应的前缀和后缀。

先来看下<trim>节点的使用场景,如果有如下SQL:

代码语言:javascript
复制
 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>

如果条件都不满足,或者只有 address 条件满足,则解析出来的SQL为 SELECT * FROM user WHERE 或SELECT * FOMR user WHERE AND address ...

可以使用 <where>标签来解决该问题, <where> 标签只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入WHERE子句。而且,若语句的开头为AND或OR,where 元素也会将它们去除.

此外,我们还可以使用 <trim>来代替,如下所示:

代码语言:javascript
复制
1<trim prefix="WHERE" prefixOverrides="AND |OR ">
2  ... 
3</trim>

它的作用是移除所有指定在 prefixOverrides 属性中的内容,并且插入 prefix 属性中指定的内容

代码语言:javascript
复制
 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 用来处理<where>标签的,前面介绍 TrimSqlNode 的时候说过,<where> 标签会自动加上前缀where,去掉and的之类的,其实 <where> 标签使用 WhereSqlNode 类来解析,而 WhereSqlNode是 TrimSqlNode 的子类,只不过是把 trim 标签的 prefix 属性设置为 where,而把 prefixToOverride 设置为 AND | OR 而已。

代码语言:javascript
复制
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}

可以看到前缀 prefix 为 where,而需要删除的前缀为 AND | OR,而后缀和需要删除的后缀为null。

SetSqlNode

SetSqlNode 主要用来解析 <set> 标签,和 <where> 标签的解析类一样,也是继承了 TrimSqlNode 类,只不过把需要添加的前缀和需要删除的后缀设置为 SET 和 逗号,即可。

代码语言:javascript
复制
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}

所以在 使用<set>标签的时候,如果最后一个条件不满足,转换为 SQL 的最后一个 逗号将会被自动去掉,如下所示:

代码语言:javascript
复制
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>

如果 bio 条件不满足,则最后一个逗号不会影响SQL的执行,应为它会被自动去掉。

ForeachSqlNode

ForeachSqlNode 主要是用来解析 <foreach> 节点的,先来看看 <foreach> 节点的用法:

代码语言:javascript
复制
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>

源码: 先来看看它的两个内部类:

PrefixedContext
代码语言:javascript
复制
 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 是用来处理#{} 占位符的,但是并未绑定参数,只是把 #{item} 转换为#{_frch_item_1} 之类的占位符。

代码语言:javascript
复制
 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

了解了 ForeachSqlNode 它的两个内部类之后,再来看看它:

代码语言:javascript
复制
 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}

在开始的例子:

代码语言:javascript
复制
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>

解析后SQL如下:SELECT * FORM user WHERE ID in (#{__frch_item_0}, #{__frch_item_1})

ChooseSqlNode

ChooseSqlNode 用来解析 <choose> 节点的,比较简单:

代码语言:javascript
复制
 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

当SQL节点经过各个SqlNode.apply()解析后,SQL语句会被传到 SqlSourceBuilder 进一步解析。SqlSourceBuilder 主要完成两部:一是解析 #{} 占位符中的属性,格式类似于#{__frc_item_0, javaType=int, jdbcType=number, typeHandler=MyTypeHander},二是把SQL中的 #{} 替换为 ?。

代码语言:javascript
复制
 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}

通过上述 SqlSourceBuilder 解析后得到 一个 StaticSqlSource 对象:

StaticSqlSource

代码语言:javascript
复制
 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

代码语言:javascript
复制
 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

最后一步使用 DynamicSqlSource 来解析动态的SQL语句:

代码语言:javascript
复制
 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

除了 DynamicSqlSource 解析动态SQL,还有 RawSqlSource 来解析 静态SQL,原理差不多。

到这里,SQL就解析完了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-03-17,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 前言
  • SqlSource
  • DynamicContext
  • SqlNode
  • StaticTextSqlNode
  • MixedSqlNode
  • TextSqlNode
  • IfSqlNode
  • TrimSqlNode
  • WhereSqlNode
  • SetSqlNode
  • ForeachSqlNode
    • PrefixedContext
    • FilteredDynamicContext
    • ForeachSqlNode
  • ChooseSqlNode
  • SqlSourceBuilder
  • StaticSqlSource
  • BoundSql
  • DynamicSqlSource
  • RawSqlSource
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档