code-learning/mybatis/19-mybatis-SQL 初始化(上)之 SqlNode.md

2319 lines
75 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 精尽 MyBatis 源码分析 —— SQL 初始化(上)之 SqlNode
# 1. 概述
本文,我们来分享 MyBatis 的脚本模块,对应 `scripting` 包。如下图所示:[![`scripting` 包](19-mybatis-SQL 初始化(上)之 SqlNode.assets/01.png)](http://static.iocoder.cn/images/MyBatis/2020_02_22/01.png)`scripting`
在 [《精尽 MyBatis 源码解析 —— 项目结构一览》](http://svip.iocoder.cn/MyBatis/intro) 中,简单介绍了这个模块如下:
> 拼凑 SQL 语句是一件烦琐且易出错的过程,为了将开发人员从这项枯燥无趣的工作中 解脱出来MyBatis 实现**动态 SQL 语句**的功能,提供了多种动态 SQL语句对应的节点。例如`<where>` 节点、`<if>` 节点、`<foreach>` 节点等 。通过这些节点的组合使用, 开发人 员可以写出几乎满足所有需求的动态 SQL 语句。
>
> MyBatis 中的 `scripting` 模块,会根据用户传入的实参,解析映射文件中定义的动态 SQL 节点,并形成数据库可执行的 SQL 语句。之后会处理 SQL 语句中的占位符,绑定用户传入的实参。
- 总结来说,`scripting` 模块,最大的作用,就是实现了 MyBatis 的**动态 SQL 语句**的功能。关于这个功能,对应文档为 [《MyBatis 文档 —— 动态 SQL》](http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html) 。
本文涉及的类如下图所示:[![类图](19-mybatis-SQL 初始化(上)之 SqlNode.assets/02.png)](http://static.iocoder.cn/images/MyBatis/2020_02_22/02.png)类图
- LanguageDriver
- SqlSource
- SqlNode
- NodeHandler
- 基于 OGNL 表达式
下面,我们来逐个来瞅瞅。
# 2. LanguageDriver
`org.apache.ibatis.scripting.LanguageDriver` ,语言驱动接口。代码如下:
```
// LanguageDriver.java
public interface LanguageDriver {
/**
* Creates a {@link ParameterHandler} that passes the actual parameters to the the JDBC statement.
*
* 创建 ParameterHandler 对象。
*
* @param mappedStatement The mapped statement that is being executed
* @param parameterObject The input parameter object (can be null)
* @param boundSql The resulting SQL once the dynamic language has been executed.
* @return
* @author Frank D. Martinez [mnesarco]
* @see DefaultParameterHandler
*/
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
/**
* Creates an {@link SqlSource} that will hold the statement read from a mapper xml file.
* It is called during startup, when the mapped statement is read from a class or an xml file.
*
* 创建 SqlSource 对象,从 Mapper XML 配置的 Statement 标签中,即 <select /> 等。
*
* @param configuration The MyBatis configuration
* @param script XNode parsed from a XML file
* @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null.
* @return
*/
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
/**
* Creates an {@link SqlSource} that will hold the statement read from an annotation.
* It is called during startup, when the mapped statement is read from a class or an xml file.
*
* 创建 SqlSource 对象,从方法注解配置,即 @Select 等。
*
* @param configuration The MyBatis configuration
* @param script The content of the annotation
* @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null.
* @return
*/
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}
```
## 2.1 XMLLanguageDriver
`org.apache.ibatis.scripting.xmltags.XMLLanguageDriver` ,实现 LanguageDriver 接口XML 语言驱动实现类。
### 2.1.1 createParameterHandler
`#createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql)` 方法,代码如下:
```
// XMLLanguageDriver.java
@Override
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
// 创建 DefaultParameterHandler 对象
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}
```
- 创建的是 DefaultParameterHandler 对象。详细解析,见 [《精尽 MyBatis 源码分析 —— SQL 初始化(下)之 SqlSource》](http://svip.iocoder.cn/MyBatis/scripting-2) 的 [「7.1 DefaultParameterHandler」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
### 2.1.2 createSqlSource
`#createSqlSource(Configuration configuration, XNode script, Class<?> parameterType)` 方法,代码如下:
```
// XMLLanguageDriver.java
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
// 创建 XMLScriptBuilder 对象,执行解析
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
```
- 创建 XMLScriptBuilder 对象,执行 `XMLScriptBuilder#parseScriptNode()` 方法,执行解析。详细解析,见 [「3. XMLScriptBuilder」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
### 2.1.3 createSqlSource
`#createSqlSource(Configuration configuration, String script, Class<?> parameterType)` 方法,代码如下:
```
// XMLLanguageDriver.java
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// issue #3
// <1> 如果是 <script> 开头,使用 XML 配置的方式,使用动态 SQL
if (script.startsWith("<script>")) {
// <1.1> 创建 XPathParser 对象,解析出 <script /> 节点
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
// <1.2> 调用上面的 #createSqlSource(...) 方法,创建 SqlSource 对象
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
// <2>
} else {
// issue #127
// <2.1> 变量替换
script = PropertyParser.parse(script, configuration.getVariables());
// <2.2> 创建 TextSqlNode 对象
TextSqlNode textSqlNode = new TextSqlNode(script);
// <2.3.1> 如果是动态 SQL ,则创建 DynamicSqlSource 对象
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
// <2.3.2> 如果非动态 SQL ,则创建 RawSqlSource 对象
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
}
```
- ```
<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」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
- `<2.3.1>` 处,如果是动态 SQL ,则创建 DynamicSqlSource 对象。详细解析,见 [《精尽 MyBatis 源码分析 —— SQL 初始化(下)之 SqlSource》](http://svip.iocoder.cn/MyBatis/scripting-2) 的 [「4.2 DynamicSqlSource」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
- `<2.3.2>` 处,如果非动态 SQL ,则创建 RawSqlSource 对象。详细解析,见 [《精尽 MyBatis 源码分析 —— SQL 初始化(下)之 SqlSource》](http://svip.iocoder.cn/MyBatis/scripting-2) 的 [「4.3 RawSqlSource」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
## 2.2 RawLanguageDriver
`org.apache.ibatis.scripting.defaults.RawLanguageDriver` ,继承 XMLLanguageDriver 类,**RawSqlSource** 语言驱动器实现类,确保创建的 SqlSource 是 **RawSqlSource** 类。代码如下:
```
// RawLanguageDriver.java
public class RawLanguageDriver extends XMLLanguageDriver {
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
// 调用父类,创建 SqlSource 对象
SqlSource source = super.createSqlSource(configuration, script, parameterType);
// 校验创建的是 RawSqlSource 对象
checkIsNotDynamic(source);
return source;
}
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// 调用父类,创建 SqlSource 对象
SqlSource source = super.createSqlSource(configuration, script, parameterType);
// 校验创建的是 RawSqlSource 对象
checkIsNotDynamic(source);
return source;
}
/**
* 校验是 RawSqlSource 对象
*
* @param source 创建的 SqlSource 对象
*/
private void checkIsNotDynamic(SqlSource source) {
if (!RawSqlSource.class.equals(source.getClass())) {
throw new BuilderException("Dynamic content is not allowed when using RAW language");
}
}
}
```
- 先基于父方法,创建 SqlSource 对象,然后再调用 `#checkIsNotDynamic(SqlSource source)` 方法,进行校验是否为 RawSqlSource 对象。
## 2.3 LanguageDriverRegistry
> 老艿艿:这个类不是 LanguageDriver 的子类。
`org.apache.ibatis.scripting.LanguageDriverRegistry` LanguageDriver 注册表。代码如下:
```
// LanguageDriverRegistry.java
public class LanguageDriverRegistry {
/**
* LanguageDriver 映射
*/
private final Map<Class<? extends LanguageDriver>, LanguageDriver> LANGUAGE_DRIVER_MAP = new HashMap<>();
/**
* 默认的 LanguageDriver 类
*/
private Class<? extends LanguageDriver> defaultDriverClass;
public void register(Class<? extends LanguageDriver> cls) {
if (cls == null) {
throw new IllegalArgumentException("null is not a valid Language Driver");
}
// 创建 cls 对应的对象,并添加到 LANGUAGE_DRIVER_MAP 中
if (!LANGUAGE_DRIVER_MAP.containsKey(cls)) {
try {
LANGUAGE_DRIVER_MAP.put(cls, cls.newInstance());
} catch (Exception ex) {
throw new ScriptingException("Failed to load language driver for " + cls.getName(), ex);
}
}
}
public void register(LanguageDriver instance) {
if (instance == null) {
throw new IllegalArgumentException("null is not a valid Language Driver");
}
// 添加到 LANGUAGE_DRIVER_MAP 中
Class<? extends LanguageDriver> cls = instance.getClass();
if (!LANGUAGE_DRIVER_MAP.containsKey(cls)) {
LANGUAGE_DRIVER_MAP.put(cls, instance);
}
}
public LanguageDriver getDriver(Class<? extends LanguageDriver> cls) {
return LANGUAGE_DRIVER_MAP.get(cls);
}
public LanguageDriver getDefaultDriver() {
return getDriver(getDefaultDriverClass());
}
public Class<? extends LanguageDriver> getDefaultDriverClass() {
return defaultDriverClass;
}
/**
* 设置 {@link #defaultDriverClass}
*
* @param defaultDriverClass 默认的 LanguageDriver 类
*/
public void setDefaultDriverClass(Class<? extends LanguageDriver> defaultDriverClass) {
// 注册到 LANGUAGE_DRIVER_MAP 中
register(defaultDriverClass);
// 设置 defaultDriverClass 属性
this.defaultDriverClass = defaultDriverClass;
}
}
```
- 比较简单,胖友自己瞅瞅就好。
### 2.3.1 初始化
在 Configuration 的构造方法中,会进行初始化。代码如下:
```
// Configuration.java
/**
* LanguageDriverRegistry 对象
*/
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
public Configuration() {
// ... 省略其它代码
// 注册到 languageRegistry 中
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
```
- 默认情况下,使用 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
/**
* 当前 SQL 的 XNode 对象
*/
private final XNode context;
/**
* 是否为动态 SQL
*/
private boolean isDynamic;
/**
* SQL 方法类型
*/
private final Class<?> parameterType;
/**
* NodeNodeHandler 的映射
*/
private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
// 初始化 nodeHandlerMap 属性
initNodeHandlerMap();
}
```
- 调用 `#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
public SqlSource parseScriptNode() {
// <1> 解析 SQL
MixedSqlNode rootSqlNode = parseDynamicTags(context);
// <2> 创建 SqlSource 对象
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
```
- `<1>` 方法,调用 `#parseDynamicTags(XNode node)` 方法,解析 SQL 成 MixedSqlNode 对象。详细解析,见 [「3.3 parseDynamicTags」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
- `<2>` 方法,根据是否是动态 SQL ,创建对应的 DynamicSqlSource 或 RawSqlSource 对象。
## 3.3 parseDynamicTags
`#parseDynamicTags(XNode node)` 方法,解析 SQL 成 MixedSqlNode 对象。代码如下:
```
// XMLScriptBuilder.java
protected MixedSqlNode parseDynamicTags(XNode node) {
// <1> 创建 SqlNode 数组
List<SqlNode> contents = new ArrayList<>();
// <2> 遍历 SQL 节点的所有子节点
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
// 当前子节点
XNode child = node.newXNode(children.item(i));
// <2.1> 如果类型是 Node.CDATA_SECTION_NODE 或者 Node.TEXT_NODE 时
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
// <2.1.1> 获得内容
String data = child.getStringBody("");
// <2.1.2> 创建 TextSqlNode 对象
TextSqlNode textSqlNode = new TextSqlNode(data);
// <2.1.2.1> 如果是动态的 TextSqlNode 对象
if (textSqlNode.isDynamic()) {
// 添加到 contents 中
contents.add(textSqlNode);
// 标记为动态 SQL
isDynamic = true;
// <2.1.2.2> 如果是非动态的 TextSqlNode 对象
} else {
// <2.1.2> 创建 StaticTextSqlNode 添加到 contents 中
contents.add(new StaticTextSqlNode(data));
}
// <2.2> 如果类型是 Node.ELEMENT_NODE
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
// <2.2.1> 根据子节点的标签,获得对应的 NodeHandler 对象
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) { // 获得不到,说明是未知的标签,抛出 BuilderException 异常
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
// <2.2.2> 执行 NodeHandler 处理
handler.handleNode(child, contents);
// <2.2.3> 标记为动态 SQL
isDynamic = true;
}
}
// <3> 创建 MixedSqlNode 对象
return new MixedSqlNode(contents);
}
```
- `<1>` 处,创建 SqlNode 数组。
- ```
<2>
```
处,遍历 SQL 节点的所有子节点,处理每个子节点成对应的 SqlNode 对象,添加到数组中。如下图,是一个示例:
![示例](19-mybatis-SQL 初始化(上)之 SqlNode.assets/03.png)
示例
- 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」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
- 关于这块逻辑,胖友可以自己多多调试下。
# 4. NodeHandler
NodeHandler ,在 XMLScriptBuilder 类中Node 处理器接口。代码如下:
```
// XMLScriptBuilder.java
private interface NodeHandler {
/**
* 处理 Node
*
* @param nodeToHandle 要处理的 XNode 节点
* @param targetContents 目标的 SqlNode 数组。实际上,被处理的 XNode 节点会创建成对应的 SqlNode 对象,添加到 targetContents 中
*/
void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}
```
NodeHandler 有多个子类实现,如下图所示:[![NodeHandler 子类](19-mybatis-SQL 初始化(上)之 SqlNode.assets/04.png)](http://static.iocoder.cn/images/MyBatis/2020_02_22/04.png)NodeHandler 子类
- 并且,每个 NodeHandler 实现类,也是 **XMLScriptBuilder** 类中。
## 4.1 BindHandler
BindHandler ,实现 NodeHandler 接口,`<bind />` 标签的处理器。代码如下:
```
// XMLScriptBuilder.java
private class BindHandler implements NodeHandler {
public BindHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 解析 name、value 属性
final String name = nodeToHandle.getStringAttribute("name");
final String expression = nodeToHandle.getStringAttribute("value");
// 创建 VarDeclSqlNode 对象
final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
// 添加到 targetContents 中
targetContents.add(node);
}
}
```
- 解析 `name`、`value` 属性,并创建 VarDeclSqlNode 对象,最后添加到 `targetContents` 中。
- 关于 VarDeclSqlNode 类,详细解析,见 [「6.1 VarDeclSqlNode」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
## 4.2 TrimHandler
TrimHandler ,实现 NodeHandler 接口,`<trim />` 标签的处理器。代码如下:
```
// XMLScriptBuilder.java
private class TrimHandler implements NodeHandler {
public TrimHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// <1> 解析内部的 SQL 节点,成 MixedSqlNode 对象
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// <2> 获得 prefix、prefixOverrides、"suffix"、suffixOverrides 属性
String prefix = nodeToHandle.getStringAttribute("prefix");
String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
String suffix = nodeToHandle.getStringAttribute("suffix");
String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
// <3> 创建 TrimSqlNode 对象
TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
// <4> 添加到 targetContents 中
targetContents.add(trim);
}
}
```
- `<1>` 处,调用 `#parseDynamicTags(XNode node)` 方法,解析内部的 SQL 节点,成 MixedSqlNode 对象。即 [「3.3 parseDynamicTags」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 的流程。
- `<2>` 处,获得 `prefix`、`prefixOverrides`、`suffix`、`suffixOverrides` 属性。
- `<3>` 处,创建 TrimSqlNode 对象。详细解析,见 [「6.2 TrimSqlNode」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
- `<4>` 处,添加到 `targetContents` 中。
## 4.3 WhereHandler
WhereHandler ,实现 NodeHandler 接口,`<where />` 标签的处理器。代码如下:
```
// XMLScriptBuilder.java
private class WhereHandler implements NodeHandler {
public WhereHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 解析内部的 SQL 节点,成 MixedSqlNode 对象
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 创建 WhereSqlNode 对象
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
// 添加到 targetContents 中
targetContents.add(where);
}
}
```
- 从实现逻辑的思路上,和 TrimHandler 是一个套路的。
- 关于 WhereSqlNode ,详细解析,见 [「6.3 WhereSqlNode」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
## 4.4 SetHandler
SetHandler ,实现 NodeHandler 接口,`<set />` 标签的处理器。代码如下:
```
// XMLScriptBuilder.java
private class SetHandler implements NodeHandler {
public SetHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 解析内部的 SQL 节点,成 MixedSqlNode 对象
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 创建 SetSqlNode 对象
SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
// 添加到 targetContents 中
targetContents.add(set);
}
}
```
- 从实现逻辑的思路上,和 TrimHandler 也是一个套路的。
- 关于 SetSqlNode ,详细解析,见 [「6.4 SetSqlNode」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
## 4.5 ForEachHandler
ForEachHandler ,实现 NodeHandler 接口,`<foreach />` 标签的处理器。代码如下:
```
// XMLScriptBuilder.java
private class ForEachHandler implements NodeHandler {
public ForEachHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 解析内部的 SQL 节点,成 MixedSqlNode 对象
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 获得 collection、item、index、open、close、separator 属性
String collection = nodeToHandle.getStringAttribute("collection");
String item = nodeToHandle.getStringAttribute("item");
String index = nodeToHandle.getStringAttribute("index");
String open = nodeToHandle.getStringAttribute("open");
String close = nodeToHandle.getStringAttribute("close");
String separator = nodeToHandle.getStringAttribute("separator");
// 创建 ForEachSqlNode 对象
ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);
// 添加到 targetContents 中
targetContents.add(forEachSqlNode);
}
}
```
- 关于 ForEachSqlNode ,详细解析,见 [「6.5 ForEachSqlNode」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
## 4.6 IfHandler
IfHandler ,实现 NodeHandler 接口,`<if />` 标签的处理器。代码如下:
```
// XMLScriptBuilder.java
private class IfHandler implements NodeHandler {
public IfHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 解析内部的 SQL 节点,成 MixedSqlNode 对象
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 获得 test 属性
String test = nodeToHandle.getStringAttribute("test");
// 创建 IfSqlNode 对象
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
// 添加到 targetContents 中
targetContents.add(ifSqlNode);
}
}
```
- 关于 ForEachSqlNode ,详细解析,见 [「6.6 IfSqlNode」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
## 4.7 OtherwiseHandler
OtherwiseHandler ,实现 NodeHandler 接口,`<otherwise />` 标签的处理器。代码如下:
```
// XMLScriptBuilder.java
private class OtherwiseHandler implements NodeHandler {
public OtherwiseHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 解析内部的 SQL 节点,成 MixedSqlNode 对象
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 添加到 targetContents 中
targetContents.add(mixedSqlNode);
}
}
```
- 对于 `<otherwise />` 标签,解析的结果是 MixedSqlNode 对象即可。因为,只要执行到,**一定满足条件**。
## 4.8 ChooseHandler
ChooseHandler ,实现 NodeHandler 接口,`<choose />` 标签的处理器。代码如下:
```
// XMLScriptBuilder.java
private class ChooseHandler implements NodeHandler {
public ChooseHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> whenSqlNodes = new ArrayList<>();
List<SqlNode> otherwiseSqlNodes = new ArrayList<>();
// 解析 `<when />` 和 `<otherwise />` 的节点们
handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
// 获得 `<otherwise />` 的节点
SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
// 创建 ChooseSqlNode 对象
ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
// 添加到 targetContents 中
targetContents.add(chooseSqlNode);
}
private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) {
List<XNode> children = chooseSqlNode.getChildren();
for (XNode child : children) {
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler instanceof IfHandler) { // 处理 `<when />` 标签的情况
handler.handleNode(child, ifSqlNodes);
} else if (handler instanceof OtherwiseHandler) { // 处理 `<otherwise />` 标签的情况
handler.handleNode(child, defaultSqlNodes);
}
}
}
// 至多允许有一个 SqlNode 节点
private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
SqlNode defaultSqlNode = null;
if (defaultSqlNodes.size() == 1) {
defaultSqlNode = defaultSqlNodes.get(0);
} else if (defaultSqlNodes.size() > 1) {
throw new BuilderException("Too many default (otherwise) elements in choose statement.");
}
return defaultSqlNode;
}
}
```
- 通过组合 IfHandler 和 OtherwiseHandler 两个处理器,实现对子节点们的解析。最终,生成 ChooseSqlNode 对象。关于 ChooseSqlNode 类,详细解析,见 。
# 5. DynamicContext
`org.apache.ibatis.scripting.xmltags.DynamicContext` ,动态 SQL ,用于每次执行 SQL 操作时,记录动态 SQL 处理后的最终 SQL 字符串。
## 5.1 构造方法
```
// DynamicContext.java
/**
* {@link #bindings} _parameter 的键,参数
*/
public static final String PARAMETER_OBJECT_KEY = "_parameter";
/**
* {@link #bindings} _databaseId 的键,数据库编号
*/
public static final String DATABASE_ID_KEY = "_databaseId";
static {
// <1.2> 设置 OGNL 的属性访问器
OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
}
/**
* 上下文的参数集合
*/
private final ContextMap bindings;
/**
* 生成后的 SQL
*/
private final StringBuilder sqlBuilder = new StringBuilder();
/**
* 唯一编号。在 {@link org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.ForEachHandler} 使用
*/
private int uniqueNumber = 0;
// 当需要使用到 OGNL 表达式时parameterObject 非空
public DynamicContext(Configuration configuration, Object parameterObject) {
// <1> 初始化 bindings 参数
if (parameterObject != null && !(parameterObject instanceof Map)) {
MetaObject metaObject = configuration.newMetaObject(parameterObject); // <1.1>
bindings = new ContextMap(metaObject);
} else {
bindings = new ContextMap(null);
}
// <2> 添加 bindings 的默认值
bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
}
```
- `<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 源码分析 —— 反射模块》](http://svip.iocoder.cn/MyBatis/reflection-package)
- `<1.2>` 处, 设置 OGNL 的属性访问器。其中OgnlRuntime 是 [`ognl`](https://mvnrepository.com/artifact/ognl/ognl) 库中的类。并且ContextMap 对应的访问器是 ContextAccessor 类。
- `<2>` 处,添加 `bindings` 的默认值。目前有 `PARAMETER_OBJECT_KEY`、`DATABASE_ID_KEY` 属性。
## 5.2 bindings 属性相关的方法
```
// DynamicContext.java
public Map<String, Object> getBindings() {
return bindings;
}
public void bind(String name, Object value) {
bindings.put(name, value);
}
```
- 可以往 `bindings` 属性中,添加新的 KV 键值对。
## 5.3 sqlBuilder 属性相关的方法
```
// DynamicContext.java
public void appendSql(String sql) {
sqlBuilder.append(sql);
sqlBuilder.append(" ");
}
public String getSql() {
return sqlBuilder.toString().trim();
}
```
- 可以不断向 `sqlBuilder` 属性中,添加 SQL **段**。
## 5.4 uniqueNumber 属性相关的方法
```
// DynamicContext.java
public int getUniqueNumber() {
return uniqueNumber++;
}
```
- 每次请求,获得新的序号。
## 5.5 ContextMap
ContextMap ,是 DynamicContext 的内部静态类,继承 HashMap 类,上下文的参数集合。代码如下:
```
// DynamicContext.java
static class ContextMap extends HashMap<String, Object> {
private static final long serialVersionUID = 2977601501966151582L;
/**
* parameter 对应的 MetaObject 对象
*/
private MetaObject parameterMetaObject;
public ContextMap(MetaObject parameterMetaObject) {
this.parameterMetaObject = parameterMetaObject;
}
@Override
public Object get(Object key) {
// 如果有 key 对应的值,直接获得
String strKey = (String) key;
if (super.containsKey(strKey)) {
return super.get(strKey);
}
// 从 parameterMetaObject 中,获得 key 对应的属性
if (parameterMetaObject != null) {
// issue #61 do not modify the context when reading
return parameterMetaObject.getValue(strKey);
}
return null;
}
}
```
- 该类在 HashMap 的基础上,增加支持对 `parameterMetaObject` 属性的访问。
## 5.6 ContextAccessor
ContextAccessor ,是 DynamicContext 的内部静态类,实现 `ognl.PropertyAccessor` 接口,上下文访问器。代码如下:
```
// DynamicContext.java
static class ContextAccessor implements PropertyAccessor {
@Override
public Object getProperty(Map context, Object target, Object name)
throws OgnlException {
Map map = (Map) target;
// 优先从 ContextMap 中,获得属性
Object result = map.get(name);
if (map.containsKey(name) || result != null) {
return result;
}
// <x> 如果没有,则从 PARAMETER_OBJECT_KEY 对应的 Map 中,获得属性
Object parameterObject = map.get(PARAMETER_OBJECT_KEY);
if (parameterObject instanceof Map) {
return ((Map) parameterObject).get(name);
}
return null;
}
@Override
public void setProperty(Map context, Object target, Object name, Object value)
throws OgnlException {
Map<Object, Object> map = (Map<Object, Object>) target;
map.put(name, value);
}
@Override
public String getSourceAccessor(OgnlContext arg0, Object arg1, Object arg2) {
return null;
}
@Override
public String getSourceSetter(OgnlContext arg0, Object arg1, Object arg2) {
return null;
}
}
```
- `<x>` 处,为什么可以访问 `PARAMETER_OBJECT_KEY` 属性,并且是 Map 类型呢?回看 DynamicContext 构造方法,就可以明白了。
# 6. SqlNode
`org.apache.ibatis.scripting.xmltags.SqlNode` SQL Node 接口,每个 XML Node 会解析成对应的 SQL Node 对象。代码如下:
```
// SqlNode.java
public interface SqlNode {
/**
* 应用当前 SQL Node 节点
*
* @param context 上下文
* @return 当前 SQL Node 节点是否应用成功。
*/
boolean apply(DynamicContext context);
}
```
- 比较难以理解的是 `#apply(DynamicContext context)` 方法,返回值类型是 `boolean` 。为什么呢?具体在 ChooseSqlNode 可找到答案。
## 6.1 VarDeclSqlNode
`org.apache.ibatis.scripting.xmltags.VarDeclSqlNode` ,实现 SqlNode 接口,`<bind />` 标签的 SqlNode 实现类。代码如下:
```
// VarDeclSqlNode.java
public class VarDeclSqlNode implements SqlNode {
/**
* 名字
*/
private final String name;
/**
* 表达式
*/
private final String expression;
public VarDeclSqlNode(String var, String exp) {
name = var;
expression = exp;
}
@Override
public boolean apply(DynamicContext context) {
// <1> 获得值
final Object value = OgnlCache.getValue(expression, context.getBindings());
// <2> 绑定到上下文
context.bind(name, value);
return true;
}
}
```
- `<1>` 处,调用 `OgnlCache#getValue(String expression, Object root)` 方法,获得表达式对应的值。详细解析,见 [「7.1 OgnlCache」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
- `<2>` 处,调用 `DynamicContext#bind(String name, Object value)` 方法,绑定到上下文。
## 6.2 TrimSqlNode
`org.apache.ibatis.scripting.xmltags.TrimSqlNode` ,实现 SqlNode 接口,`<trim />` 标签的 SqlNode 实现类。
关于 TrimSqlNode 的概念,可能从类名上无法很好的反馈出来,可以看下文档 [《MyBatis 文档 —— 动态 SQL》](http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html) ,搜 [「trim」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 关键字。
另外,在下文中,我们会看到,`trim />` 标签是 `<where />` 和 `<set />` 标签的**基础**。
### 6.2.1 构造方法
```
// TrimSqlNode.java
/**
* 内含的 SqlNode 节点
*/
private final SqlNode contents;
/**
* 前缀
*/
private final String prefix;
/**
* 后缀
*/
private final String suffix;
/**
* 需要被删除的前缀
*/
private final List<String> prefixesToOverride;
/**
* 需要被删除的后缀
*/
private final List<String> suffixesToOverride;
private final Configuration configuration;
public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
}
protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {
this.contents = contents;
this.prefix = prefix;
this.prefixesToOverride = prefixesToOverride;
this.suffix = suffix;
this.suffixesToOverride = suffixesToOverride;
this.configuration = configuration;
}
```
- `#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
@Override
public boolean apply(DynamicContext context) {
// <1> 创建 FilteredDynamicContext 对象
FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
// <2> 执行 contents 的应用
boolean result = contents.apply(filteredDynamicContext);
// <3> 执行 FilteredDynamicContext 的应用
filteredDynamicContext.applyAll();
return result;
}
```
- `<1>` 处,创建 FilteredDynamicContext 对象。关于 FilteredDynamicContext 类,在 [「6.2.3 FilteredDynamicContext」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
- `<2>` 处,执行 `contents` 的应用。
- `<3>` 处,调用 `FilteredDynamicContext#applyAll()` 方法,执行 FilteredDynamicContext 的应用。
### 6.2.3 FilteredDynamicContext
FilteredDynamicContext ,是 TrimSqlNode 的内部类,继承 DynamicContext 类,支持 **trim** 逻辑的 DynamicContext 实现类。
#### 6.2.3.1 构造方法
```
// TrimSqlNode.java
/**
* 委托的 DynamicContext 对象
*/
private DynamicContext delegate;
/**
* 是否 prefix 已经被应用
*/
private boolean prefixApplied;
/**
* 是否 suffix 已经被应用
*/
private boolean suffixApplied;
/**
* StringBuilder 对象
*
* @see #appendSql(String)
*/
private StringBuilder sqlBuffer;
public FilteredDynamicContext(DynamicContext delegate) {
super(configuration, null);
this.delegate = delegate;
this.prefixApplied = false;
this.suffixApplied = false;
this.sqlBuffer = new StringBuilder();
}
@Override
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
@Override
public String getSql() {
return delegate.getSql();
}
```
- 每个实现的方法,都直接调用 `delegate` 对应的方法。除了 `#append(String sql)` 方法,**以外**。详细解析,见 [「7.2.3.2」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
#### 7.2.3.2 append
```
// TrimSqlNode.java
@Override
public void appendSql(String sql) {
sqlBuffer.append(sql);
}
```
- 该方法,将拼接的 `sql` ,暂时存储到 `sqlBuffer` 中。
- 最终,会通过 `#applyAll()` 方法,将 `sqlBuffer` 处理完后,添加回 `delegate.sqlBuffer` 中。
#### 7.2.3.3 applyAll
```
// TrimSqlNode.java
public void applyAll() {
// <1> trim 掉多余的空格,生成新的 sqlBuffer 对象
sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
// <2> 将 sqlBuffer 大写,生成新的 trimmedUppercaseSql 对象
String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
// <3> 应用 TrimSqlNode 的 trim 逻辑
if (trimmedUppercaseSql.length() > 0) {
applyPrefix(sqlBuffer, trimmedUppercaseSql);
applySuffix(sqlBuffer, trimmedUppercaseSql);
}
// <4> 将结果,添加到 delegate 中
delegate.appendSql(sqlBuffer.toString());
}
```
- `<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
public class WhereSqlNode extends TrimSqlNode {
private static List<String> prefixList = Arrays.asList("AND ", "OR ", "AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");
public WhereSqlNode(Configuration configuration, SqlNode contents) {
super(configuration, contents, "WHERE", prefixList, null, null);
}
}
```
- 这就是为什么,说 WhereHandler 和 TrimHandler 是一个套路的原因。
## 6.4 SetSqlNode
`org.apache.ibatis.scripting.xmltags.SetSqlNode` ,继承 TrimSqlNode 类,`<set />` 标签的 SqlNode 实现类。代码如下:
```
// WhereSqlNode.java
public class SetSqlNode extends TrimSqlNode {
private static List<String> suffixList = Collections.singletonList(",");
public SetSqlNode(Configuration configuration, SqlNode contents) {
super(configuration, contents, "SET", null, null, suffixList);
}
}
```
- 这就是为什么,说 SetHandler 和 TrimHandler 是一个套路的原因。
## 6.5 ForEachSqlNode
`org.apache.ibatis.scripting.xmltags.ForEachSqlNode` ,实现 SqlNode 接口,`<foreach />` 标签的 SqlNode 实现类。
### 6.5.1 构造方法
```
// ForEachSqlNode.java
private final ExpressionEvaluator evaluator;
/**
* 集合的表达式
*/
private final String collectionExpression;
private final SqlNode contents;
private final String open;
private final String close;
private final String separator;
/**
* 集合项
*/
private final String item;
/**
* 索引变量
*/
private final String index;
private final Configuration configuration;
public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, String index, String item, String open, String close, String separator) {
this.evaluator = new ExpressionEvaluator();
this.collectionExpression = collectionExpression;
this.contents = contents;
this.open = open;
this.close = close;
this.separator = separator;
this.index = index;
this.item = item;
this.configuration = configuration;
}
```
### 6.5.2 apply
```
// ForEachSqlNode.java
@Override
public boolean apply(DynamicContext context) {
Map<String, Object> bindings = context.getBindings();
// <1> 获得遍历的集合的 Iterable 对象,用于遍历。
final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
if (!iterable.iterator().hasNext()) {
return true;
}
boolean first = true;
// <2> 添加 open 到 SQL 中
applyOpen(context);
int i = 0;
for (Object o : iterable) {
// <3> 记录原始的 context 对象
DynamicContext oldContext = context;
// <4> 生成新的 context
if (first || separator == null) {
context = new PrefixedContext(context, "");
} else {
context = new PrefixedContext(context, separator);
}
// <5> 获得唯一编号
int uniqueNumber = context.getUniqueNumber();
// Issue #709
// <6> 绑定到 context 中
if (o instanceof Map.Entry) {
@SuppressWarnings("unchecked")
Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
applyIndex(context, mapEntry.getKey(), uniqueNumber);
applyItem(context, mapEntry.getValue(), uniqueNumber);
} else {
applyIndex(context, i, uniqueNumber);
applyItem(context, o, uniqueNumber);
}
// <7> 执行 contents 的应用
contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
// <8> 判断 prefix 是否已经插入
if (first) {
first = !((PrefixedContext) context).isPrefixApplied();
}
// <9> 恢复原始的 context 对象
context = oldContext;
i++;
}
// <10> 添加 close 到 SQL 中
applyClose(context);
// <11> 移除 index 和 item 对应的绑定
context.getBindings().remove(item);
context.getBindings().remove(index);
return true;
}
```
- 这个方法的逻辑,相对会比较复杂。胖友最好自己也调试下。
- 我们假设以如下查询为示例:
```
<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」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
- `<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」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
- `<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;
}
```
- 上述实例,效果如下图:[![效果](19-mybatis-SQL 初始化(上)之 SqlNode.assets/05.png)](http://static.iocoder.cn/images/MyBatis/2020_02_22/05.png)效果
- 另外,此处也根据是否为 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
private class PrefixedContext extends DynamicContext {
private final DynamicContext delegate;
private final String prefix;
/**
* 是否已经应用 prefix
*/
private boolean prefixApplied;
public PrefixedContext(DynamicContext delegate, String prefix) {
super(configuration, null);
this.delegate = delegate;
this.prefix = prefix;
this.prefixApplied = false;
}
public boolean isPrefixApplied() {
return prefixApplied;
}
@Override
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public void appendSql(String sql) {
// 如果未应用 prefix ,并且,方法参数 sql 非空
// 则添加 prefix 到 delegate 中,并标记 prefixApplied 为 true ,表示已经应用
if (!prefixApplied && sql != null && sql.trim().length() > 0) {
delegate.appendSql(prefix);
prefixApplied = true;
}
// 添加 sql 到 delegate 中
delegate.appendSql(sql);
}
@Override
public String getSql() {
return delegate.getSql();
}
@Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
}
```
- `prefix` 属性,虽然属性命名上是 `prefix` ,但是对应到 ForEachSqlNode 的 `separator` 属性。
- 重心在于 `#appendSql(String sql)` 方法的实现。逻辑还是比较简单的,就是判断之前是否添加过 `prefix` ,没有就进行添加。而判断的依据,就是 `prefixApplied` 标识。
### 6.5.4 FilteredDynamicContext
FilteredDynamicContext ,是 ForEachSqlNode 的内部类,继承 DynamicContext 类,实现子节点访问 `<foreach />` 标签中的变量的**替换**的 DynamicContext 实现类。说起来比较绕,我们直接来看代码。代码如下:
```
// ForEachSqlNode.java
private static class FilteredDynamicContext extends DynamicContext {
private final DynamicContext delegate;
/**
* 唯一标识 {@link DynamicContext#getUniqueNumber()}
*/
private final int index;
/**
* 索引变量 {@link ForEachSqlNode#index}
*/
private final String itemIndex;
/**
* 集合项 {@link ForEachSqlNode#item}
*/
private final String item;
public FilteredDynamicContext(Configuration configuration, DynamicContext delegate, String itemIndex, String item, int i) {
super(configuration, null);
this.delegate = delegate;
this.index = i;
this.itemIndex = itemIndex;
this.item = item;
}
@Override
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public String getSql() {
return delegate.getSql();
}
@Override
public void appendSql(String sql) {
GenericTokenParser parser = new GenericTokenParser("#{", "}", content -> {
// 将对 item 的访问,替换成 itemizeItem(item, index) 。
String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
// 将对 itemIndex 的访问,替换成 itemizeItem(itemIndex, index) 。
if (itemIndex != null && newContent.equals(content)) {
newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index));
}
// 返回
return "#{" + newContent + "}";
});
// 执行 GenericTokenParser 的解析
// 添加到 delegate 中
delegate.appendSql(parser.parse(sql));
}
@Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
}
```
- 核心方法是 `#appendSql(String sql)` 方法的重写。胖友可以集合下图示例,理解下具体的代码实现。[![效果](19-mybatis-SQL 初始化(上)之 SqlNode.assets/06.png)](http://static.iocoder.cn/images/MyBatis/2020_02_22/06.png)效果
- 那可能胖友会有疑惑,如果变成这样,具体的值,在哪里设置呢?答案在 DefaultParameterHandler 类中。所以,继续往下看。哈哈哈哈。
## 6.6 IfSqlNode
`org.apache.ibatis.scripting.xmltags.IfSqlNode` ,实现 SqlNode 接口,`<if />` 标签的 SqlNode 实现类。代码如下:
```
// IfSqlNode.java
public class IfSqlNode implements SqlNode {
private final ExpressionEvaluator evaluator;
/**
* 判断表达式
*/
private final String test;
/**
* 内嵌的 SqlNode 节点
*/
private final SqlNode contents;
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
@Override
public boolean apply(DynamicContext context) {
// <1> 判断是否符合条件
if (evaluator.evaluateBoolean(test, context.getBindings())) {
// <2> 符合,执行 contents 的应用
contents.apply(context);
// 返回成功
return true;
}
// <3> 不符合,返回失败
return false;
}
}
```
- `<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
public class ChooseSqlNode implements SqlNode {
/**
* <otherwise /> 标签对应的 SqlNode 节点
*/
private final SqlNode defaultSqlNode;
/**
* <when /> 标签对应的 SqlNode 节点数组
*/
private final List<SqlNode> ifSqlNodes;
public ChooseSqlNode(List<SqlNode> ifSqlNodes, SqlNode defaultSqlNode) {
this.ifSqlNodes = ifSqlNodes;
this.defaultSqlNode = defaultSqlNode;
}
@Override
public boolean apply(DynamicContext context) {
// <1> 先判断 <when /> 标签中,是否有符合条件的节点。
// 如果有,则进行应用。并且只因应用一个 SqlNode 对象
for (SqlNode sqlNode : ifSqlNodes) {
if (sqlNode.apply(context)) {
return true;
}
}
// <2> 再判断 <otherwise /> 标签,是否存在
// 如果存在,则进行应用
if (defaultSqlNode != null) {
defaultSqlNode.apply(context);
return true;
}
// <3> 返回都失败
return false;
}
}
```
- `<1>` 处,先判断 `<when />` 标签中,是否有符合条件的节点。如果有,则进行应用。并且只因应用**一个** SqlNode 对象。这里,我们就看到了,`SqlNode#apply(context)` 方法,返回 `true` 或 `false` 的用途了。
- `<2>` 处,再判断 `<otherwise />` 标签,是否存在。如果存在,则进行应用。
- `<3>` 处,返回都失败。
## 6.8 StaticTextSqlNode
`org.apache.ibatis.scripting.xmltags.StaticTextSqlNode` ,实现 SqlNode 接口,静态文本的 SqlNode 实现类。代码如下:
```
// StaticTextSqlNode.java
public class StaticTextSqlNode implements SqlNode {
/**
* 静态文本
*/
private final String text;
public StaticTextSqlNode(String text) {
this.text = text;
}
@Override
public boolean apply(DynamicContext context) {
// 直接拼接到 context 中
context.appendSql(text);
return true;
}
}
```
## 6.9 TextSqlNode
`org.apache.ibatis.scripting.xmltags.TextSqlNode` ,实现 SqlNode 接口,文本的 SqlNode 实现类。相比 StaticTextSqlNode 的实现来说TextSqlNode 不确定是否为静态文本,所以提供 `#isDynamic()` 方法,进行判断是否为动态文本。
### 6.9.1 构造方法
```
// TextSqlNode.java
/**
* 文本
*/
private final String text;
/**
* 目前该属性只在单元测试中使用,暂时无视
*/
private final Pattern injectionFilter;
public TextSqlNode(String text) {
this(text, null);
}
public TextSqlNode(String text, Pattern injectionFilter) {
this.text = text;
this.injectionFilter = injectionFilter;
}
```
### 6.9.2 isDynamic
`#isDynamic()` 方法,判断是否为动态文本。代码如下:
```
// TextSqlNode.java
public boolean isDynamic() {
// <1> 创建 DynamicCheckerTokenParser 对象
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
// <2> 创建 GenericTokenParser 对象
GenericTokenParser parser = createParser(checker);
// <3> 执行解析
parser.parse(text);
// <4> 判断是否为动态文本
return checker.isDynamic();
}
```
- `<2>` 处,调用 `#createParser(TokenHandler handler)` 方法,创建 GenericTokenParser 对象。代码如下:
```
// TextSqlNode.java
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
```
- 通过这个方法,我们可以得知,只要存在 `${xxx}` 对,就认为是动态文本。另外,如果胖友遗忘了 GenericTokenParser 类,回到 [《精尽 MyBatis 源码分析 —— 解析器模块》](http://svip.iocoder.cn/MyBatis/parsing-package) 再复习下。
- `<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;
}
@Override
public String handleToken(String content) {
// 当检测到 token ,标记为动态文本
this.isDynamic = true;
return null;
}
}
```
- DynamicCheckerTokenParser 是 TextSqlNode 的内部静态类。
- 当 GenericTokenParser 发现 token 时,会调用 `#handleToken(String content)` 方法,标记 `isDynamic` 为 `true` ,即标记为动态文本。
- `<4>` 处,调用 `DynamicCheckerTokenParser#isDynamic()` 方法,判断是否为动态文本。
### 6.9.3 apply
```
// TextSqlNode.java
@Override
public boolean apply(DynamicContext context) {
// <1> 创建 BindingTokenParser 对象
// <2> 创建 GenericTokenParser 对象
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
// <3> 执行解析
// <4> 将解析的结果,添加到 context 中
context.appendSql(parser.parse(text));
return true;
}
```
- `<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;
}
@Override
public String handleToken(String content) {
// 初始化 value 属性到 context 中
Object parameter = context.getBindings().get("_parameter");
if (parameter == null) {
context.getBindings().put("value", null);
} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
context.getBindings().put("value", parameter);
}
// 使用 OGNL 表达式,获得对应的值
Object value = OgnlCache.getValue(content, context.getBindings());
String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
checkInjection(srtValue);
// 返回该值
return srtValue;
}
private void checkInjection(String value) {
if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {
throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern());
}
}
}
```
- 对于该方法,如下的示例:
```
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
public class MixedSqlNode implements SqlNode {
/**
* 内嵌的 SqlNode 数组
*/
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
// 遍历 SqlNode 数组,逐个应用
for (SqlNode sqlNode : contents) {
sqlNode.apply(context);
}
return true;
}
}
```
- MixedSqlNode 内含有 SqlNode 数组。
- 在 `#apply(DynamicContext context)` 方法中,遍历 SqlNode 数组,逐个应用。
# 7. OGNL 相关
对 OGNL 不熟悉的胖友,可以简单看看如下三篇文章:
- 金剑 [《OGNL 语言介绍与实践》](https://www.ibm.com/developerworks/cn/opensource/os-cn-ognl/index.html)
- 阿春阿晓 [《OGNL表达式介绍》](https://www.cnblogs.com/renchunxiao/p/3423299.html)
- 岑宇 [《Ognl表达式基本原理和使用方法》](https://www.cnblogs.com/cenyu/p/6233942.html)
## 7.1 OgnlCache
`org.apache.ibatis.scripting.xmltags.OgnlCache` OGNL 缓存类。代码如下:
```
// OgnlCache.java
public final class OgnlCache {
/**
* OgnlMemberAccess 单例
*/
private static final OgnlMemberAccess MEMBER_ACCESS = new OgnlMemberAccess();
/**
* OgnlClassResolver 单例
*/
private static final OgnlClassResolver CLASS_RESOLVER = new OgnlClassResolver();
/**
* 表达式的缓存的映射
*
* KEY表达式
* VALUE表达式的缓存 @see #parseExpression(String)
*/
private static final Map<String, Object> expressionCache = new ConcurrentHashMap<>();
private OgnlCache() {
// Prevent Instantiation of Static Class
}
public static Object getValue(String expression, Object root) {
try {
// <1> 创建 OGNL Context 对象
Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);
// <2> 解析表达式
// <3> 获得表达式对应的值
return Ognl.getValue(parseExpression(expression), context, root);
} catch (OgnlException e) {
throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
}
}
private static Object parseExpression(String expression) throws OgnlException {
Object node = expressionCache.get(expression);
if (node == null) {
node = Ognl.parseExpression(expression);
expressionCache.put(expression, node);
}
return node;
}
}
```
- ```
<1>
```
处,调用
```
Ognl#createDefaultContext(Object root, MemberAccess memberAccess, ClassResolver classResolver, TypeConverter converter)
```
方法,创建 OGNL Context 对象。其中:
- `memberAccess` 属性,使用 `MEMBER_ACCESS` 单例。详细解析,见 [「7.2 OgnlMemberAccess」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
- `classResolver` 属性,使用 `CLASS_RESOLVER` 单例。详细解析,见 [「7.3 OgnlClassResolver」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 。
- `<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
class OgnlMemberAccess implements MemberAccess {
/**
* 是否可以修改成员的可访问
*/
private final boolean canControlMemberAccessible;
OgnlMemberAccess() {
this.canControlMemberAccessible = Reflector.canControlMemberAccessible();
}
@Override
public Object setup(Map context, Object target, Member member, String propertyName) {
Object result = null;
// 判断是否可以修改
if (isAccessible(context, target, member, propertyName)) {
AccessibleObject accessible = (AccessibleObject) member;
// 不可访问,则设置为可访问
if (!accessible.isAccessible()) {
// 标记原来是不可访问的
result = Boolean.FALSE;
// 修改可访问
accessible.setAccessible(true);
}
}
return result;
}
@Override
public void restore(Map context, Object target, Member member, String propertyName,
Object state) {
if (state != null) {
// 修改为原来的可访问
((AccessibleObject) member).setAccessible(((Boolean) state));
}
}
@Override
public boolean isAccessible(Map context, Object target, Member member, String propertyName) {
return canControlMemberAccessible;
}
}
```
- `#setup(...)` 方法,和 `#restore(...)` 方法是**相辅相成**的。
- `#setup(...)` 方法,当发现需要访问的成员 `member` 不可访问,并且可以修改时,会修改为可访问,并返回之前是不可访问( `result = false` )。
- `#restore(...)` 方法,如果 `#setup(...)` 方法,修改了可访问级别,那么 `state != null` 成立,就会进行恢复。
## 7.3 OgnlClassResolver
`org.apache.ibatis.scripting.xmltags.OgnlClassResolver`,继承 `ognl.DefaultClassResolver` 类OGNL 类解析器实现类。代码如下:
```
// OgnlClassResolver.java
public class OgnlClassResolver extends DefaultClassResolver {
@Override
protected Class toClassForName(String className) throws ClassNotFoundException {
return Resources.classForName(className);
}
}
```
- 在方法内部,使用的是 `org.apache.ibatis.io.Resources` 类。
## 7.4 ExpressionEvaluator
`org.apache.ibatis.scripting.xmltags.ExpressionEvaluator` OGNL 表达式计算器。代码如下:
```
// ExpressionEvaluator.java
public class ExpressionEvaluator {
/**
* 判断表达式对应的值,是否为 true
*
* @param expression 表达式
* @param parameterObject 参数对象
* @return 是否为 true
*/
public boolean evaluateBoolean(String expression, Object parameterObject) {
// 获得表达式对应的值
Object value = OgnlCache.getValue(expression, parameterObject);
// 如果是 Boolean 类型,直接判断
if (value instanceof Boolean) {
return (Boolean) value;
}
// 如果是 Number 类型,则判断不等于 0
if (value instanceof Number) {
return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;
}
// 如果是其它类型,判断非空
return value != null;
}
/**
* 获得表达式对应的集合
*
* @param expression 表达式
* @param parameterObject 参数对象
* @return 迭代器对象
*/
public Iterable<?> evaluateIterable(String expression, Object parameterObject) {
// 获得表达式对应的值
Object value = OgnlCache.getValue(expression, parameterObject);
if (value == null) {
throw new BuilderException("The expression '" + expression + "' evaluated to a null value.");
}
// 如果是 Iterable 类型,直接返回
if (value instanceof Iterable) {
return (Iterable<?>) value;
}
// 如果是数组类型,则返回数组
if (value.getClass().isArray()) {
// the array may be primitive, so Arrays.asList() may throw
// a ClassCastException (issue 209). Do the work manually
// Curse primitives! :) (JGB)
int size = Array.getLength(value);
List<Object> answer = new ArrayList<>();
for (int i = 0; i < size; i++) {
Object o = Array.get(value, i);
answer.add(o);
}
return answer;
}
// 如果是 Map 类型,则返回 Map.entrySet 集合
if (value instanceof Map) {
return ((Map) value).entrySet();
}
throw new BuilderException("Error evaluating expression '" + expression + "'. Return value (" + value + ") was not iterable.");
}
}
```
- 两个方法,比较简单,胖友自己看看。
# 666. 彩蛋
本文略长,所以考虑了下,还是拆成上下两篇,避免胖友看的苦闷,艿艿写的苦逼。
参考和推荐如下文章:
- 祖大俊 [《Mybatis3.4.x技术内幕十七Mybatis之动态Sql设计原本](https://my.oschina.net/zudajun/blog/735553)
- 祖大俊 [《Mybatis3.4.x技术内幕十八Mybatis之动态Sql设计原本](https://my.oschina.net/zudajun/blog/735731)
- 徐郡明 [《MyBatis 技术内幕》](https://item.jd.com/12125531.html) 的 [「3.2 SqlNode&SqlSource」](https://svip.iocoder.cn/MyBatis/scripting-1/#) 小节