mybatis-sqlsource
1. 概述
本文,我们来分享 MyBatis 的脚本模块,对应 scripting 包。如下图所示:
在 《精尽 MyBatis 源码解析 —— 项目结构一览》 中,简单介绍了这个模块如下:
拼凑 SQL 语句是一件烦琐且易出错的过程,为了将开发人员从这项枯燥无趣的工作中 解脱出来,MyBatis 实现动态 SQL 语句的功能,提供了多种动态 SQL语句对应的节点。例如
<where>节点、<if>节点、<foreach>节点等 。通过这些节点的组合使用, 开发人 员可以写出几乎满足所有需求的动态 SQL 语句。MyBatis 中的
scripting模块,会根据用户传入的实参,解析映射文件中定义的动态 SQL 节点,并形成数据库可执行的 SQL 语句。之后会处理 SQL 语句中的占位符,绑定用户传入的实参。
- 总结来说,
scripting模块,最大的作用,就是实现了 MyBatis 的动态 SQL 语句的功能。关于这个功能,对应文档为 《MyBatis 文档 —— 动态 SQL》 。
本文涉及的类如下图所示:
- LanguageDriver
- SqlSource
- SqlNode
- NodeHandler
- 基于 OGNL 表达式
下面,我们来逐个来瞅瞅。
2. LanguageDriver
org.apache.ibatis.scripting.LanguageDriver ,语言驱动接口。代码如下:
// LanguageDriver.java
|
2.1 XMLLanguageDriver
org.apache.ibatis.scripting.xmltags.XMLLanguageDriver ,实现 LanguageDriver 接口,XML 语言驱动实现类。
2.1.1 createParameterHandler
#createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) 方法,代码如下:
// XMLLanguageDriver.java
|
- 创建的是 DefaultParameterHandler 对象。详细解析,见 《精尽 MyBatis 源码分析 —— SQL 初始化(下)之 SqlSource》 的 「7.1 DefaultParameterHandler」 。
2.1.2 createSqlSource
#createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) 方法,代码如下:
// XMLLanguageDriver.java
|
- 创建 XMLScriptBuilder 对象,执行
XMLScriptBuilder#parseScriptNode()方法,执行解析。详细解析,见 「3. XMLScriptBuilder」 。
2.1.3 createSqlSource
#createSqlSource(Configuration configuration, String script, Class<?> parameterType) 方法,代码如下:
// XMLLanguageDriver.java
|
<1>处,如果是<script>开头,使用 XML 配置的方式,使用动态 SQL 。没使用过的胖友,可以看看 《spring boot(8)-mybatis三种动态sql》 。<1.1>处,创建 XPathParser 对象,解析出 XML<script />节点。<1.2>处,调用#createSqlSource(Configuration configuration, XNode script, Class<?> parameterType)方法,创建 SqlSource 对象。
<2>处,<2.1>处,变量替换。<2.2.>处,创建 TextSqlNode 对象。详细解析,见 「6.9 TextSqlNode」 。<2.3.1>处,如果是动态 SQL ,则创建 DynamicSqlSource 对象。详细解析,见 《精尽 MyBatis 源码分析 —— SQL 初始化(下)之 SqlSource》 的 「4.2 DynamicSqlSource」 。<2.3.2>处,如果非动态 SQL ,则创建 RawSqlSource 对象。详细解析,见 《精尽 MyBatis 源码分析 —— SQL 初始化(下)之 SqlSource》 的 「4.3 RawSqlSource」 。
2.2 RawLanguageDriver
org.apache.ibatis.scripting.defaults.RawLanguageDriver ,继承 XMLLanguageDriver 类,RawSqlSource 语言驱动器实现类,确保创建的 SqlSource 是 RawSqlSource 类。代码如下:
// RawLanguageDriver.java
|
- 先基于父方法,创建 SqlSource 对象,然后再调用
#checkIsNotDynamic(SqlSource source)方法,进行校验是否为 RawSqlSource 对象。
2.3 LanguageDriverRegistry
老艿艿:这个类不是 LanguageDriver 的子类。
org.apache.ibatis.scripting.LanguageDriverRegistry ,LanguageDriver 注册表。代码如下:
// LanguageDriverRegistry.java
|
- 比较简单,胖友自己瞅瞅就好。
2.3.1 初始化
在 Configuration 的构造方法中,会进行初始化。代码如下:
// Configuration.java
|
- 默认情况下,使用 XMLLanguageDriver 类。
-
大多数情况下,我们不会去设置使用的 LanguageDriver 类,而是使用 XMLLanguageDriver 类。从
#getLanguageDriver(Class<? extends LanguageDriver> langClass)方法,可知。代码如下:// Configuration.java
public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
// 获得 langClass 类
if (langClass != null) {
configuration.getLanguageRegistry().register(langClass);
} else { // 如果为空,则使用默认类
langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
}
// 获得 LanguageDriver 对象
return configuration.getLanguageRegistry().getDriver(langClass);
}
3. XMLScriptBuilder
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder ,继承 BaseBuilder 抽象类,XML 动态语句( SQL )构建器,负责将 SQL 解析成 SqlSource 对象。
3.1 构造方法
// XMLScriptBuilder.java
|
-
调用
#initNodeHandlerMap()方法,初始化nodeHandlerMap属性。代码如下:// XMLScriptBuilder.java
private void initNodeHandlerMap() {
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
}- 我们可以看到,
nodeHandlerMap的 KEY 是熟悉的 MyBatis 的自定义的 XML 标签。并且,每个标签对应专属的一个 NodeHandler 实现类。
- 我们可以看到,
3.2 parseScriptNode
#parseScriptNode() 方法,负责将 SQL 解析成 SqlSource 对象。代码如下:
// XMLScriptBuilder.java
|
<1>方法,调用#parseDynamicTags(XNode node)方法,解析 SQL 成 MixedSqlNode 对象。详细解析,见 「3.3 parseDynamicTags」 。<2>方法,根据是否是动态 SQL ,创建对应的 DynamicSqlSource 或 RawSqlSource 对象。
3.3 parseDynamicTags
#parseDynamicTags(XNode node) 方法,解析 SQL 成 MixedSqlNode 对象。代码如下:
// XMLScriptBuilder.java
|
<1>处,创建 SqlNode 数组。<2>处,遍历 SQL 节点的所有子节点,处理每个子节点成对应的 SqlNode 对象,添加到数组中。如下图,是一个示例:
- XML 本身是个嵌套的树结构,所以最终的结果,也是嵌套的 SqlNode 数组结构。
<2.1>处,如果节点类型是Node.CDATA_SECTION_NODE或者Node.TEXT_NODE时。<2.1.1>处, 获得节点的内容。<2.1.2>处,创建 TextSqlNode 对象。<2.1.2.1>处,如果是动态的 TextSqlNode 对象,则添加到contents中,并标记为动态 SQL 。例如:SELECT * FROM subject。<2.1.2.2>处,如果非动态的 TextSqlNode 对象,则创建 StaticTextSqlNode 对象,并添加到contents中。例如:id = ${id}。
<2.2>处,如果节点类型是Node.ELEMENT_NODE时。例如:<where> <choose> <when test="${id != null}"> id = ${id} </when> </choose> </where>。<2.2.1>处,根据子节点的标签,获得对应的 NodeHandler 对象。<2.2.2>处,执行 NodeHandler 处理。<2.2.3>处,标记为动态 SQL 。
<3>处,将contents数组,封装成 MixedSqlNode 对象。详细解析,见 「6.10 MixedSqlNode」 。- 关于这块逻辑,胖友可以自己多多调试下。
4. NodeHandler
NodeHandler ,在 XMLScriptBuilder 类中,Node 处理器接口。代码如下:
// XMLScriptBuilder.java
|
NodeHandler 有多个子类实现,如下图所示:
- 并且,每个 NodeHandler 实现类,也是 XMLScriptBuilder 类中。
4.1 BindHandler
BindHandler ,实现 NodeHandler 接口,<bind /> 标签的处理器。代码如下:
// XMLScriptBuilder.java
|
- 解析
name、value属性,并创建 VarDeclSqlNode 对象,最后添加到targetContents中。 - 关于 VarDeclSqlNode 类,详细解析,见 「6.1 VarDeclSqlNode」 。
4.2 TrimHandler
TrimHandler ,实现 NodeHandler 接口,<trim /> 标签的处理器。代码如下:
// XMLScriptBuilder.java
|
<1>处,调用#parseDynamicTags(XNode node)方法,解析内部的 SQL 节点,成 MixedSqlNode 对象。即 「3.3 parseDynamicTags」 的流程。<2>处,获得prefix、prefixOverrides、suffix、suffixOverrides属性。<3>处,创建 TrimSqlNode 对象。详细解析,见 「6.2 TrimSqlNode」 。<4>处,添加到targetContents中。
4.3 WhereHandler
WhereHandler ,实现 NodeHandler 接口,<where /> 标签的处理器。代码如下:
// XMLScriptBuilder.java
|
- 从实现逻辑的思路上,和 TrimHandler 是一个套路的。
- 关于 WhereSqlNode ,详细解析,见 「6.3 WhereSqlNode」 。
4.4 SetHandler
SetHandler ,实现 NodeHandler 接口,<set /> 标签的处理器。代码如下:
// XMLScriptBuilder.java
|
- 从实现逻辑的思路上,和 TrimHandler 也是一个套路的。
- 关于 SetSqlNode ,详细解析,见 「6.4 SetSqlNode」 。
4.5 ForEachHandler
ForEachHandler ,实现 NodeHandler 接口,<foreach /> 标签的处理器。代码如下:
// XMLScriptBuilder.java
|
- 关于 ForEachSqlNode ,详细解析,见 「6.5 ForEachSqlNode」 。
4.6 IfHandler
IfHandler ,实现 NodeHandler 接口,<if /> 标签的处理器。代码如下:
// XMLScriptBuilder.java
|
- 关于 ForEachSqlNode ,详细解析,见 「6.6 IfSqlNode」 。
4.7 OtherwiseHandler
OtherwiseHandler ,实现 NodeHandler 接口,<otherwise /> 标签的处理器。代码如下:
// XMLScriptBuilder.java
|
- 对于
<otherwise />标签,解析的结果是 MixedSqlNode 对象即可。因为,只要执行到,一定满足条件。
4.8 ChooseHandler
ChooseHandler ,实现 NodeHandler 接口,<choose /> 标签的处理器。代码如下:
// XMLScriptBuilder.java
|
- 通过组合 IfHandler 和 OtherwiseHandler 两个处理器,实现对子节点们的解析。最终,生成 ChooseSqlNode 对象。关于 ChooseSqlNode 类,详细解析,见 。
5. DynamicContext
org.apache.ibatis.scripting.xmltags.DynamicContext ,动态 SQL ,用于每次执行 SQL 操作时,记录动态 SQL 处理后的最终 SQL 字符串。
5.1 构造方法
// DynamicContext.java
|
-
<1>处,初始化bindings参数,创建 ContextMap 对象。parameterObject方法参数,当需要使用到 OGNL 表达式时,parameterObject才会非空。下文,我们将会看到为什么。-
<1.1>处,我们可以看到,调用Configuration#newMetaObject(Object object)方法,创建 MetaObject 对象,实现对parameterObject参数的访问。代码如下:// Configuration.java
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
public MetaObject newMetaObject(Object object) {
return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}- 关于 MetaObject ,在 《精尽 MyBatis 源码分析 —— 反射模块》
<1.2>处, 设置 OGNL 的属性访问器。其中,OgnlRuntime 是ognl库中的类。并且,ContextMap 对应的访问器是 ContextAccessor 类。
<2>处,添加bindings的默认值。目前有PARAMETER_OBJECT_KEY、DATABASE_ID_KEY属性。
5.2 bindings 属性相关的方法
// DynamicContext.java
|
- 可以往
bindings属性中,添加新的 KV 键值对。
5.3 sqlBuilder 属性相关的方法
// DynamicContext.java
|
- 可以不断向
sqlBuilder属性中,添加 SQL 段。
5.4 uniqueNumber 属性相关的方法
// DynamicContext.java
|
- 每次请求,获得新的序号。
5.5 ContextMap
ContextMap ,是 DynamicContext 的内部静态类,继承 HashMap 类,上下文的参数集合。代码如下:
// DynamicContext.java
|
- 该类在 HashMap 的基础上,增加支持对
parameterMetaObject属性的访问。
5.6 ContextAccessor
ContextAccessor ,是 DynamicContext 的内部静态类,实现 ognl.PropertyAccessor 接口,上下文访问器。代码如下:
// DynamicContext.java
|
<x>处,为什么可以访问PARAMETER_OBJECT_KEY属性,并且是 Map 类型呢?回看 DynamicContext 构造方法,就可以明白了。
6. SqlNode
org.apache.ibatis.scripting.xmltags.SqlNode ,SQL Node 接口,每个 XML Node 会解析成对应的 SQL Node 对象。代码如下:
// SqlNode.java
|
- 比较难以理解的是
#apply(DynamicContext context)方法,返回值类型是boolean。为什么呢?具体在 ChooseSqlNode 可找到答案。
6.1 VarDeclSqlNode
org.apache.ibatis.scripting.xmltags.VarDeclSqlNode ,实现 SqlNode 接口,<bind /> 标签的 SqlNode 实现类。代码如下:
// VarDeclSqlNode.java
|
<1>处,调用OgnlCache#getValue(String expression, Object root)方法,获得表达式对应的值。详细解析,见 「7.1 OgnlCache」 。<2>处,调用DynamicContext#bind(String name, Object value)方法,绑定到上下文。
6.2 TrimSqlNode
org.apache.ibatis.scripting.xmltags.TrimSqlNode ,实现 SqlNode 接口,<trim /> 标签的 SqlNode 实现类。
关于 TrimSqlNode 的概念,可能从类名上无法很好的反馈出来,可以看下文档 《MyBatis 文档 —— 动态 SQL》 ,搜 「trim」 关键字。
另外,在下文中,我们会看到,trim /> 标签是 <where /> 和 <set /> 标签的基础。
6.2.1 构造方法
// TrimSqlNode.java
|
-
#parseOverrides(String overrides)方法,使用|分隔字符串成字符串数组,并都转换成大写。代码如下:// TrimSqlNode.java
private static List<String> parseOverrides(String overrides) {
if (overrides != null) {
final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
final List<String> list = new ArrayList<>(parser.countTokens());
while (parser.hasMoreTokens()) {
list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
}
return list;
}
return Collections.emptyList();
}
6.2.2 apply
// TrimSqlNode.java
|
<1>处,创建 FilteredDynamicContext 对象。关于 FilteredDynamicContext 类,在 「6.2.3 FilteredDynamicContext」 。<2>处,执行contents的应用。<3>处,调用FilteredDynamicContext#applyAll()方法,执行 FilteredDynamicContext 的应用。
6.2.3 FilteredDynamicContext
FilteredDynamicContext ,是 TrimSqlNode 的内部类,继承 DynamicContext 类,支持 trim 逻辑的 DynamicContext 实现类。
6.2.3.1 构造方法
// TrimSqlNode.java
|
- 每个实现的方法,都直接调用
delegate对应的方法。除了#append(String sql)方法,以外。详细解析,见 「7.2.3.2」 。
7.2.3.2 append
// TrimSqlNode.java
|
- 该方法,将拼接的
sql,暂时存储到sqlBuffer中。 - 最终,会通过
#applyAll()方法,将sqlBuffer处理完后,添加回delegate.sqlBuffer中。
7.2.3.3 applyAll
// TrimSqlNode.java
|
<1>处,trim 掉多余的空格,生成新的sqlBuffer对象。<2>处,将sqlBuffer大写,生成新的trimmedUppercaseSql对象。为什么呢?因为,TrimSqlNode 对prefixesToOverride和suffixesToOverride属性,都进行了大写的处理,需要保持统一。但是,又不能直接修改sqlBuffer,因为这样就相当于修改了原始的 SQL 。-
<3>处,应用 TrimSqlNode 的 trim 逻辑。-
#applyPrefix(StringBuilder sql, String trimmedUppercaseSql)方法,代码如下:// TrimSqlNode.java
private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
if (!prefixApplied) {
prefixApplied = true;
// prefixesToOverride 非空,先删除
if (prefixesToOverride != null) {
for (String toRemove : prefixesToOverride) {
if (trimmedUppercaseSql.startsWith(toRemove)) {
sql.delete(0, toRemove.trim().length());
break;
}
}
}
// prefix 非空,再添加
if (prefix != null) {
sql.insert(0, " ");
sql.insert(0, prefix);
}
}
}- x
-
#applySuffix(StringBuilder sql, String trimmedUppercaseSql)方法,代码如下:// TrimSqlNode.java
private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
if (!suffixApplied) {
suffixApplied = true;
// suffixesToOverride 非空,先删除
if (suffixesToOverride != null) {
for (String toRemove : suffixesToOverride) {
if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
int start = sql.length() - toRemove.trim().length();
int end = sql.length();
sql.delete(start, end);
break;
}
}
}
// suffix 非空,再添加
if (suffix != null) {
sql.append(" ");
sql.append(suffix);
}
}
}- x
-
-
<4>处,将结果,添加到delegate中。
6.3 WhereSqlNode
org.apache.ibatis.scripting.xmltags.WhereSqlNode ,继承 TrimSqlNode 类,<where /> 标签的 SqlNode 实现类。代码如下:
// WhereSqlNode.java
|
- 这就是为什么,说 WhereHandler 和 TrimHandler 是一个套路的原因。
6.4 SetSqlNode
org.apache.ibatis.scripting.xmltags.SetSqlNode ,继承 TrimSqlNode 类,<set /> 标签的 SqlNode 实现类。代码如下:
// WhereSqlNode.java
|
- 这就是为什么,说 SetHandler 和 TrimHandler 是一个套路的原因。
6.5 ForEachSqlNode
org.apache.ibatis.scripting.xmltags.ForEachSqlNode ,实现 SqlNode 接口,<foreach /> 标签的 SqlNode 实现类。
6.5.1 构造方法
// ForEachSqlNode.java
|
6.5.2 apply
// ForEachSqlNode.java
|
- 这个方法的逻辑,相对会比较复杂。胖友最好自己也调试下。
-
我们假设以如下查询为示例:
<select id="getSubjectList" parameterType="List" resultType="List">
SELECT id FROM subject
WHERE id IN
<foreach collection="ids" index="idx" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</select> -
<1>处,调用ExpressionEvaluator#evaluateBoolean(String expression, Object parameterObject)方法,获得遍历的集合的 Iterable 对象,用于遍历。详细解析,见 「7.4 ExpressionEvaluator」 。 -
<2>处,调用#applyOpen(DynamicContext context)方法,添加open到 SQL 中。代码如下:// ForEachSqlNode.java
private void applyOpen(DynamicContext context) {
if (open != null) {
context.appendSql(open);
}
} -
下面开始,我们要遍历
iterable了。 <3>处,记录原始的context对象。为什么呢?因为<4>处,会生成新的context对象。<4>处,生成新的context对象。类型为 PrefixedContext 对象,只有在非首次,才会传入separator属性。因为,PrefixedContext 处理的是集合元素之间的分隔符。详细解析,见 「6.5.3 PrefixedContext」 。<5>处,获得唯一编号。-
<6>处,绑定到context中。调用的两个方法,代码如下:// ForEachSqlNode.java
public static final String ITEM_PREFIX = "__frch_";
private void applyIndex(DynamicContext context, Object o, int i) {
if (index != null) {
context.bind(index, o);
context.bind(itemizeItem(index, i), o);
}
}
private void applyItem(DynamicContext context, Object o, int i) {
if (item != null) {
context.bind(item, o);
context.bind(itemizeItem(item, i), o);
}
}
private static String itemizeItem(String item, int i) {
return ITEM_PREFIX + item + "_" + i;
}- 上述实例,效果如下图:
![效果]()
-
另外,此处也根据是否为 Map.Entry 类型,分成了两种情况。官方文档说明如下:
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象传递给 foreach 作为集合参数。当使用可迭代对象或者数组时,index 是当前迭代的次数,item 的值是本次迭代获取的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
- 上述实例,效果如下图:
-
<7>处,执行contents的应用。- 例如说,此处
contents就是上述示例的" #{item}"。 - 另外,进一步将
context对象,封装成 FilteredDynamicContext 对象。
- 例如说,此处
<8>处,判断prefix是否已经插入。如果是,则first会被设置为false。然后,胖友回过头看看<4>处的逻辑,是不是清晰多了。<9>处,恢复原始的context对象。然后,胖友回过头看看<3>处的逻辑,是不是清晰多了。-
<10>处,调用#applyClose(DynamicContext context)方法,添加close到 SQL 中。代码如下:// ForEachSqlNode.java
private void applyClose(DynamicContext context) {
if (close != null) {
context.appendSql(close);
}
} -
<11>处,移除index和item属性对应的绑定。这两个绑定,是在<6>处被添加的。
6.5.3 PrefixedContext
PrefixedContext ,是 ForEachSqlNode 的内部类,继承 DynamicContext 类,支持添加 <foreach /> 标签中,多个元素之间的分隔符的 DynamicContext 实现类。代码如下:
// ForEachSqlNode.java
|
prefix属性,虽然属性命名上是prefix,但是对应到 ForEachSqlNode 的separator属性。- 重心在于
#appendSql(String sql)方法的实现。逻辑还是比较简单的,就是判断之前是否添加过prefix,没有就进行添加。而判断的依据,就是prefixApplied标识。
6.5.4 FilteredDynamicContext
FilteredDynamicContext ,是 ForEachSqlNode 的内部类,继承 DynamicContext 类,实现子节点访问 <foreach /> 标签中的变量的替换的 DynamicContext 实现类。说起来比较绕,我们直接来看代码。代码如下:
// ForEachSqlNode.java
|
- 核心方法是
#appendSql(String sql)方法的重写。胖友可以集合下图示例,理解下具体的代码实现。![效果]()
- 那可能胖友会有疑惑,如果变成这样,具体的值,在哪里设置呢?答案在 DefaultParameterHandler 类中。所以,继续往下看。哈哈哈哈。
6.6 IfSqlNode
org.apache.ibatis.scripting.xmltags.IfSqlNode ,实现 SqlNode 接口,<if /> 标签的 SqlNode 实现类。代码如下:
// IfSqlNode.java
|
<1>处,会调用ExpressionEvaluator#evaluateBoolean(String expression, Object parameterObject)方法,判断是否符合条件。<2>处,如果符合条件,则执行contents的应用,并返回成功true。<3>处,如果不符条件,则返回失败false。 😈 此处,终于出现一个返回false的情况,最终会在 ChooseSqlNode 中,会看到true和false的用处。
6.7 ChooseSqlNode
org.apache.ibatis.scripting.xmltags.ChooseSqlNode ,实现 SqlNode 接口,<choose /> 标签的 SqlNode 实现类。代码如下:
// ChooseSqlNode.java
|
<1>处,先判断<when />标签中,是否有符合条件的节点。如果有,则进行应用。并且只因应用一个 SqlNode 对象。这里,我们就看到了,SqlNode#apply(context)方法,返回true或false的用途了。<2>处,再判断<otherwise />标签,是否存在。如果存在,则进行应用。<3>处,返回都失败。
6.8 StaticTextSqlNode
org.apache.ibatis.scripting.xmltags.StaticTextSqlNode ,实现 SqlNode 接口,静态文本的 SqlNode 实现类。代码如下:
// StaticTextSqlNode.java
|
6.9 TextSqlNode
org.apache.ibatis.scripting.xmltags.TextSqlNode ,实现 SqlNode 接口,文本的 SqlNode 实现类。相比 StaticTextSqlNode 的实现来说,TextSqlNode 不确定是否为静态文本,所以提供 #isDynamic() 方法,进行判断是否为动态文本。
6.9.1 构造方法
// TextSqlNode.java
|
6.9.2 isDynamic
#isDynamic() 方法,判断是否为动态文本。代码如下:
// TextSqlNode.java
|
-
<2>处,调用#createParser(TokenHandler handler)方法,创建 GenericTokenParser 对象。代码如下:// TextSqlNode.java
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}- 通过这个方法,我们可以得知,只要存在
${xxx}对,就认为是动态文本。另外,如果胖友遗忘了 GenericTokenParser 类,回到 《精尽 MyBatis 源码分析 —— 解析器模块》 再复习下。
- 通过这个方法,我们可以得知,只要存在
<3>处,调用GenericTokenParser#parse(String text)方法,执行解析,寻找${xxx}对。-
<1>处,创建 DynamicCheckerTokenParser 对象。代码如下:// TextSqlNode.java
private static class DynamicCheckerTokenParser implements TokenHandler {
/**
* 是否为动态文本
*/
private boolean isDynamic;
public DynamicCheckerTokenParser() {
// Prevent Synthetic Access
}
public boolean isDynamic() {
return isDynamic;
}
- DynamicCheckerTokenParser 是 TextSqlNode 的内部静态类。
- 当 GenericTokenParser 发现 token 时,会调用
#handleToken(String content)方法,标记isDynamic为true,即标记为动态文本。
<4>处,调用DynamicCheckerTokenParser#isDynamic()方法,判断是否为动态文本。
6.9.3 apply
// TextSqlNode.java
|
<2>处,创建 GenericTokenParser 对象<3>处,调用GenericTokenParser#parse(String text)方法,执行解析。当解析到${xxx}时,会调用 BindingTokenParser 的#handleToken(String content)方法,执行相应的逻辑。<4>处,将解析的结果,添加到context中。-
<1>处,创建 BindingTokenParser 对象。代码如下:// TextSqlNode.java
private static class BindingTokenParser implements TokenHandler {
private DynamicContext context;
private Pattern injectionFilter;
public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
this.context = context;
this.injectionFilter = injectionFilter;
}
-
对于该方法,如下的示例:
SELECT * FROM subject WHERE id = ${id}id = ${id}的${id}部分,将被替换成对应的具体编号。例如说,id为 1 ,则会变成SELECT * FROM subject WHERE id = 1。
-
而对于如下的示例:
SELECT * FROM subject WHERE id = #{id}id = #{id}的#{id}部分,则不会进行替换。
-
6.10 MixedSqlNode
org.apache.ibatis.scripting.xmltags.MixedSqlNode ,实现 SqlNode 接口,混合的 SqlNode 实现类。代码如下:
// MixedSqlNode.java
|
- MixedSqlNode 内含有 SqlNode 数组。
- 在
#apply(DynamicContext context)方法中,遍历 SqlNode 数组,逐个应用。
7. OGNL 相关
对 OGNL 不熟悉的胖友,可以简单看看如下三篇文章:
- 金剑 《OGNL 语言介绍与实践》
- 阿春阿晓 《OGNL表达式介绍》
- 岑宇 《Ognl表达式基本原理和使用方法》
7.1 OgnlCache
org.apache.ibatis.scripting.xmltags.OgnlCache ,OGNL 缓存类。代码如下:
// OgnlCache.java
|
<1>处,调用Ognl#createDefaultContext(Object root, MemberAccess memberAccess, ClassResolver classResolver, TypeConverter converter)方法,创建 OGNL Context 对象。其中:memberAccess属性,使用MEMBER_ACCESS单例。详细解析,见 「7.2 OgnlMemberAccess」 。classResolver属性,使用CLASS_RESOLVER单例。详细解析,见 「7.3 OgnlClassResolver」 。
<2>处,调用#parseExpression(String expression)方法,解析表达式。在该方法中,我们会看到,会缓存解析的表达式到expressionCache中。<3>处,调用Ognl#getValue((Object tree, Map context, Object root)方法,获得表达式对应的值。
7.2 OgnlMemberAccess
org.apache.ibatis.scripting.xmltags.OgnlMemberAccess,实现 ognl.MemberAccess 接口,OGNL 成员访问器实现类。代码如下:
// OgnlMemberAccess.java
|
#setup(...)方法,和#restore(...)方法是相辅相成的。#setup(...)方法,当发现需要访问的成员member不可访问,并且可以修改时,会修改为可访问,并返回之前是不可访问(result = false)。#restore(...)方法,如果#setup(...)方法,修改了可访问级别,那么state != null成立,就会进行恢复。
7.3 OgnlClassResolver
org.apache.ibatis.scripting.xmltags.OgnlClassResolver,继承 ognl.DefaultClassResolver 类,OGNL 类解析器实现类。代码如下:
// OgnlClassResolver.java
|
- 在方法内部,使用的是
org.apache.ibatis.io.Resources类。
7.4 ExpressionEvaluator
org.apache.ibatis.scripting.xmltags.ExpressionEvaluator ,OGNL 表达式计算器。代码如下:
// ExpressionEvaluator.java
|



浙公网安备 33010602011771号