本文首发于个人公众号 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 接口的定义:
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 节点的解析。
DynamicContextDynamicContext 该类主要用来存放解析动态 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 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 的定义如下:
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 节点是如何解析的.
StaticTextSqlNodeStaticTextSqlNode 表示的是 静态文本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}MixedSqlNodeMixedSqlNode 表示有多个 SqlNode节点,apply() 方法依次调用对应 SqlNode 节点的apply()方法:
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}TextSqlNodeTextSqlNode 表示的是包含有 ${} 占位符的动态 SQL 语句,它会调用 GenericTokenParser 工具类来解析 ${} 占位符,关于 GenericTokenParser 工具类,可以参考 Mybatis 解析配置文件的源码解析
比如有段 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}IfSqlNodeIfSqlNode 用来解析 <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}TrimSqlNodeTrimSqlNode 用来解析<trim>节点,它会根据子节点的解析结果添加或删除相应的前缀和后缀。
先来看下<trim>节点的使用场景,如果有如下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>如果条件都不满足,或者只有 address 条件满足,则解析出来的SQL为 SELECT * FROM user WHERE 或SELECT * FOMR user WHERE AND address ...
可以使用 <where>标签来解决该问题, <where> 标签只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入WHERE子句。而且,若语句的开头为AND或OR,where 元素也会将它们去除.
此外,我们还可以使用 <trim>来代替,如下所示:
1<trim 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 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}WhereSqlNodeWhereSqlNode 用来处理<where>标签的,前面介绍 TrimSqlNode 的时候说过,<where> 标签会自动加上前缀where,去掉and的之类的,其实 <where> 标签使用 WhereSqlNode 类来解析,而 WhereSqlNode是 TrimSqlNode 的子类,只不过是把 trim 标签的 prefix 属性设置为 where,而把 prefixToOverride 设置为 AND | OR 而已。
1public class WhereSqlNode extends TrimSqlNode { 2 3 private static List<String> 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。
SetSqlNodeSetSqlNode 主要用来解析 <set> 标签,和 <where> 标签的解析类一样,也是继承了 TrimSqlNode 类,只不过把需要添加的前缀和需要删除的后缀设置为 SET 和 逗号,即可。
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 的最后一个 逗号将会被自动去掉,如下所示:
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的执行,应为它会被自动去掉。
ForeachSqlNodeForeachSqlNode 主要是用来解析 <foreach> 节点的,先来看看 <foreach> 节点的用法:
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 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}FilteredDynamicContextFilteredDynamicContext 是用来处理#{} 占位符的,但是并未绑定参数,只是把 #{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 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}在开始的例子:
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})
ChooseSqlNodeChooseSqlNode 用来解析 <choose> 节点的,比较简单:
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中的 #{} 替换为 ?。
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 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最后一步使用 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.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就解析完了。
---来自腾讯云社区的---Java技术大杂烩
微信扫一扫打赏
支付宝扫一扫打赏