前言
在上两篇文章 和 中分析了 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技术大杂烩):