前言
在上两篇文章 和 中分析了 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
接口的定义:
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 片段的。
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 HashMapString, 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
的定义如下:
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
属性中即可。
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()
方法:
1public class MixedSqlNode implements SqlNode {
2 private ListSqlNode contents;
3 public MixedSqlNode(ListSqlNode 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
工具类,可以参考
比如有段 SQL 为 name=${name},参数为 name=zhangsan,则通过
TextSqlNode
解析后的 SQL 片段为 name=zhangsan,并把该 SQL 片段添加到
DynamicContext
中。源码如下:
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
标签的用法:
1 if test="username != null"
2 username=#{username}
3/if
解析如下:
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:
1select 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
来代替,如下所示:
1trim prefix="WHERE" prefixOverrides="AND |OR "
2 ...
3/trim
它的作用是移除所有指定在
prefixOverrides
属性中的内容,并且插入
prefix
属性中指定的内容
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 ListString prefixesToOverride;
10 // 删除指定的后缀
11 private ListString 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 ListString parseOverrides(String overrides) {
20 if (overrides != null) {
21 // 按 | 分割,放到集合中
22 final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
23 final ListString list = new ArrayListString(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
而已。
1public class WhereSqlNode extends TrimSqlNode {
2
3 private static ListString prefixList = Arrays.asList("AND ","OR ","ANDn", "ORn", "ANDr", "ORr", "ANDt", "ORt");
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
和 逗号
,
即可。
1public class SetSqlNode extends TrimSqlNode {
2
3 private static ListString suffixList = Arrays.asList(",");
4
5 public SetSqlNode(Configuration configuration,SqlNode contents) {
6 super(configuration, contents, "SET", null, null, suffixList);
7
8}
所以在 使用
set
标签的时候,如果最后一个条件不满足,转换为 SQL 的最后一个 逗号将会被自动去掉,如下所示:
1update 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
节点的用法:
1select 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
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}
之类的占位符。
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 它的两个内部类之后,再来看看它:
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 MapString, 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.EntryObject, Object mapEntry = (Map.EntryObject, 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}
在开始的例子:
1select 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
节点的,比较简单:
1public class ChooseSqlNode implements SqlNode {
2 // 对应 otherwise 节点
3 private SqlNode defaultSqlNode;
4 // 对应when 节点
5 private ListSqlNode ifSqlNodes;
6
7 public ChooseSqlNode(ListSqlNode 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中的
#{}
替换为
?
。
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, MapString, 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 ListParameterMapping parameterMappings = new ArrayListParameterMapping();
26 private Class? parameterType;
27 private MetaObject metaParameters;
28
29 public ParameterMappingTokenHandler(Configuration configuration, Class? parameterType, MapString, Object additionalParameters) {
30 super(configuration);
31 this.parameterType = parameterType;
32 this.metaParameters = configuration.newMetaObject(additionalParameters);
33 }
34
35 public ListParameterMapping 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
1public class StaticSqlSource implements SqlSource {
2 // SQL
3 private String sql;
4 // 参数的属性集合
5 private ListParameterMapping parameterMappings;
6 private Configuration configuration;
7
8 public StaticSqlSource(Configuration configuration, String sql, ListParameterMapping 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 ListParameterMapping parameterMappings;
6 // 传入的实际参数
7 private Object parameterObject;
8 // 空的hashmap,之后中复制 DynamicContext.bindings 中的内容
9 private MapString, Object additionalParameters;
10 //additionalParameters集合对象的 MetaObject 对象
11 private MetaObject metaParameters;
12}
DynamicSqlSource
最后一步使用
DynamicSqlSource
来解析动态的SQL语句:
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.EntryString, Object entry : context.getBindings().entrySet()) {
24 boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
25 }
26 return boundSql;
27 }
28}
RawSqlSource
除了 DynamicSqlSource 解析动态SQL,还有
RawSqlSource
来解析 静态SQL,原理差不多。
到这里,SQL就解析完了。
原文始发于微信公众号(Java技术大杂烩):