精尽MyBatis源码分析 - MyBatis初始化(三)之 SQL 初始化(上)
作者:互联网
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址、Mybatis-Spring 源码分析 GitHub 地址、Spring-Boot-Starter 源码分析 GitHub 地址)进行阅读
MyBatis 版本:3.5.2
MyBatis-Spring 版本:2.0.3
MyBatis-Spring-Boot-Starter 版本:2.1.4
MyBatis的初始化
在MyBatis初始化过程中,大致会有以下几个步骤:
-
创建
Configuration
全局配置对象,会往TypeAliasRegistry
别名注册中心添加Mybatis需要用到的相关类,并设置默认的语言驱动类为XMLLanguageDriver
-
加载
mybatis-config.xml
配置文件、Mapper接口中的注解信息和XML映射文件,解析后的配置信息会形成相应的对象并保存到Configuration全局配置对象中 -
构建
DefaultSqlSessionFactory
对象,通过它可以创建DefaultSqlSession
对象,MyBatis中SqlSession
的默认实现类
因为整个初始化过程涉及到的代码比较多,所以拆分成了四个模块依次对MyBatis的初始化进行分析:
- 《MyBatis初始化(一)之加载mybatis-config.xml》
- 《MyBatis初始化(二)之加载Mapper接口与XML映射文件》
- 《MyBatis初始化(三)之SQL初始化(上)》
- 《MyBatis初始化(四)之SQL初始化(下)》
由于在MyBatis的初始化过程中去解析Mapper接口与XML映射文件涉及到的篇幅比较多,XML映射文件的解析过程也比较复杂,所以才分成了后面三个模块,逐步分析,这样便于理解
初始化(三)之SQL初始化(上)
在前面的MyBatis初始化相关文档中已经大致讲完了MyBatis初始化的整个流程,其中遗漏了一部分,就是在解析<select /> <insert /> <update /> <delete />
节点的过程中,是如何解析SQL语句,如何实现动态SQL语句,最终会生成一个org.apache.ibatis.mapping.SqlSource
对象的,对于这烦琐且易出错的过程,我们来看看MyBatis如何实现的?
我们回顾org.apache.ibatis.builder.xml.XMLStatementBuilder
的parseStatementNode()
解析 Statement 节点时,通过下面的方法创建对应的SqlSource
对象
// 创建对应的 SqlSource 对象,保存了该节点下 SQL 相关信息
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
langDriver
是从Configuration全局配置对象中获取的默认实现类,对应的也就是XMLLanguageDriver
,在Configuration初始化的时候设置的
public Configuration() {
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
主要包路径:org.apache.ibatis.scripting、org.apache.ibatis.builder、org.apache.ibatis.mapping
主要涉及到的类:
-
org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
:语言驱动接口的默认实现,创建ParameterHandler参数处理器对象和SqlSource资源对象 -
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder
:继承 BaseBuilder 抽象类,负责将SQL脚本(XML或者注解中定义的SQL语句)解析成SqlSource(DynamicSqlSource或者RawSqlSource)资源对象 -
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.NodeHandler
:定义在XMLScriptBuilder
内部的一个接口,用于处理MyBatis自定义标签(<if /> <foreach />
等),生成对应的SqlNode对象,不同的实现类处理不同的标签 -
org.apache.ibatis.scripting.xmltags.DynamicContext
:解析动态SQL语句时的上下文,用于解析SQL时,记录动态SQL处理后的SQL语句,内部提供ContextMap对象保存上下文的参数 -
org.apache.ibatis.scripting.xmltags.SqlNode
:SQL Node接口,每个XML Node会解析成对应的SQL Node对象,通过上下文可以对动态SQL进行逻辑处理,生成需要的结果 -
org.apache.ibatis.scripting.xmltags.OgnlCache
:用于处理Ognl表达式
语言驱动接口的实现类如下图所示:
LanguageDriver
org.apache.ibatis.scripting.LanguageDriver
:语言驱动接口,代码如下:
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 SQL 资源
*/
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 SQL 资源
*/
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}
定义了三个方法:
-
createParameterHandler
:获取 ParameterHandler 参数处理器对象 -
createSqlSource
:创建 SqlSource 对象,解析 Mapper XML 配置的 Statement 标签中,即<select /> <update /> <delete /> <insert />
-
createSqlSource
:创建 SqlSource 对象,从方法注解配置,即 @Select 等
XMLLanguageDriver
org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
:语言驱动接口的默认实现,代码如下:
public class XMLLanguageDriver implements LanguageDriver {
@Override
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
// 创建 DefaultParameterHandler 对象
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}
/**
* 用于解析 XML 映射文件中的 SQL
*
* @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 SQL 资源
*/
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
// 创建 XMLScriptBuilder 对象,执行解析
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
/**
* 用于解析注解中的 SQL
*
* @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 SQL 资源
*/
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// issue #3
// <1> 如果是 <script> 开头,表示是在注解中使用的动态 SQL
if (script.startsWith("<script>")) {
// <1.1> 创建 XPathParser 对象,解析出 <script /> 节点
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
} else {
// issue #127
// <2.1> 变量替换
script = PropertyParser.parse(script, configuration.getVariables());
// <2.2> 创建 TextSqlNode 对象
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) { // <2.3.1> 如果是动态 SQL ,则创建 DynamicSqlSource 对象
return new DynamicSqlSource(configuration, textSqlNode);
} else { // <2.3.2> 如果非动态 SQL ,则创建 RawSqlSource 对象
return new RawSqlSource(configuration, script, parameterType);
}
}
}
}
实现了LanguageDriver接口:
-
创建
DefaultParameterHandler
默认参数处理器并返回 -
解析 XML 映射文件中的 SQL,通过创建
XMLScriptBuilder
对象,调用其parseScriptNode()
方法解析 -
解析注解定义的 SQL
- 如果是
<script>
开头,表示是在注解中使用的动态 SQL,将其转换成 XNode 然后调用上述方法,不了解的可以看看MyBatis三种动态SQL配置方式 - 先将注解中定义的 SQL 中包含的变量进行转换,然后创建对应的 SqlSource 对象
- 如果是
RawLanguageDriver
org.apache.ibatis.scripting.defaults.RawLanguageDriver
:继承了XMLLanguageDriver,在的基础上增加了是否为静态SQL语句的校验,也就是判断创建的 SqlSource 是否为 RawSqlSource 静态 SQL 资源
XMLScriptBuilder
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder
:继承 BaseBuilder 抽象类,负责将 SQL 脚本(XML或者注解中定义的 SQL )解析成 SqlSource 对象
构造方法
public class XMLScriptBuilder extends BaseBuilder {
/**
* 当前 SQL 的 XNode 对象
*/
private final XNode context;
/**
* 是否为动态 SQL
*/
private boolean isDynamic;
/**
* SQL 的 Java 入参类型
*/
private final Class<?> parameterType;
/**
* NodeNodeHandler 的映射
*/
private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();
public XMLScriptBuilder(Configuration configuration, XNode context) {
this(configuration, context, null);
}
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
initNodeHandlerMap();
}
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());
}
}
在构造函数中会初始化 NodeHandler 处理器,分别用于处理不同的MyBatis自定义的XML标签,例如<if /> <where /> <foreach />
等标签
parseScriptNode方法
parseScriptNode()
方法将 SQL 脚本(XML或者注解中定义的 SQL )解析成 SqlSource
对象,代码如下:
public SqlSource parseScriptNode() {
// 解析 XML 或者注解中定义的 SQL
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
// 动态语句,使用了 ${} 也算
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
- 通过调用
parseDynamicTags(XNode node)
方法,将解析 SQL 成MixedSqlNode
对象,主要是将一整个 SQL 解析成一系列的 SqlNode 对象 - 如果是动态SQL语句,使用了MyBatis自定义的XML标签(
<if />
等)或者使用了${}
,则封装成DynamicSqlSource
对象 - 否则就是静态SQL语句,封装成
RawSqlSource
对象
parseDynamicTags方法
parseDynamicTags()
将 SQL 脚本(XML或者注解中定义的 SQL )解析成MixedSqlNode
对象,代码如下:
protected MixedSqlNode parseDynamicTags(XNode node) {
// <1> 创建 SqlNode 数组
List<SqlNode> contents = new ArrayList<>();
/*
* <2> 遍历 SQL 节点中所有子节点
* 这里会对该节点内的所有内容进行处理然后返回 NodeList 对象
* 1. 文本内容会被解析成 '<#text></#text>' 节点,就算一个换行符也会解析成这个
* 2. <![CDATA[ content ]]> 会被解析成 '<#cdata-section>content</#cdata-section>' 节点
* 3. 其他动态<if /> <where />
*/
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 // <![CDATA[ ]]>节点
|| child.getNode().getNodeType() == Node.TEXT_NODE) { // 纯文本
// <2.1.1> 获得内容
String data = child.getStringBody("");
// <2.1.2> 创建 TextSqlNode 对象
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) { // <2.1.2.1> 如果是动态的 TextSqlNode 对象,也就是使用了 '${}'
// 添加到 contents 中
contents.add(textSqlNode);
// 标记为动态 SQL
isDynamic = true;
} else { // <2.1.2.2> 如果是非动态的 TextSqlNode 对象,没有使用 '${}'
// <2.1.2> 创建 StaticTextSqlNode 添加到 contents 中
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 <2.2> 如果类型是 Node.ELEMENT_NODE
// <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 数组 contents
,用于保存解析 SQL 后的一些列 SqlNode 对象
<2>
获取定义的 SQL 节点中所有子节点,返回一个 NodeList 对象,这个对象中包含了该 SQL 节点内的所有信息,然后逐个遍历子节点
1. 其中文本内容会被解析成`<#text></#text>`节点,就算一个换行符也会解析成这个
2. `<![CDATA[ ]]>` 会被解析成 `<#cdata-section></#cdata-section>` 节点
3. 还有其他MyBatis自定义的标签`<if /> <where />`等等
<2.1>
如果子节点是<#text />
或者<#cdata-section />
类型
<2.1.1>
获取子节点的文本内容
<2.1.2>
创建 TextSqlNode 对象
<2.1.2.1>
调用 TextSqlNode
的 isDynamic() 方法,点击去该进去看看就知道了,如果文本中使用了${}
,则标记为动态 SQL 语句,将其添加至 contents
数组中
<2.1.2.2>
否则就是静态文本内容,创建对应的 StaticTextSqlNode
对象,将其添加至 contents
数组中
<2.2>
如果类型是 Node.ELEMENT_NODE
时,也就是 MyBatis 的自定义标签
<2.2.1>
根据子节点的标签名称,获得对应的 NodeHandler
对象
<2.2.2>
执行NodeHandler
的handleNode
方法处理该节点,创建不通类型的 SqlNode 并添加到 contents
数组中,如何处理的在下面讲述
<2.2.3>
标记为动态 SQL 语句
<3>
最后将创建 contents
封装成 MixedSqlNode
对象
NodeHandler
XMLScriptBuilder
的内部接口,用于处理MyBatis自定义标签,接口实现类如下图所示:
代码如下:
private interface NodeHandler {
/**
* 处理 Node
*
* @param nodeToHandle 要处理的 XNode 节点
* @param targetContents 目标的 SqlNode 数组。实际上,被处理的 XNode 节点会创建成对应的 SqlNode 对象,添加到 targetContents 中
*/
void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}
这些 NodeHandler 实现类都定义在 XMLScriptBuilder 内部,用于处理不同标签,我们逐个来看
BindHandler
实现了NodeHandler接口,<bind />
标签的处理器,代码如下:
/**
* <bind />元素允许你在 OGNL 表达式(SQL语句)以外创建一个变量,并将其绑定到当前的上下文
*/
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.add(node);
}
}
-
获取
<bind />
标签的name和value属性 -
根据这些属性创建一个
VarDeclSqlNode
对象 -
添加到
targetContents
集合中
例如这样配置:
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
TrimHandler
实现了NodeHandler接口,<trim />
标签的处理器,代码如下:
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);
targetContents.add(trim);
}
}
-
继续调用
parseDynamicTags
方法解析<if />
标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象 -
获得
prefix
、prefixOverrides
、suffix
、suffixOverrides
属性 -
根据上面获取到的属性创建
TrimSqlNode
对象 -
添加到
targetContents
集合中
WhereHandler
实现了NodeHandler接口,<where />
标签的处理器,代码如下:
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.add(where);
}
}
- 继续调用
parseDynamicTags
方法解析<where />
标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象 - 创建
WhereSqlNode
对象,该对象继承了TrimSqlNode
,自定义前缀(WHERE)和需要删除的前缀(AND、OR等) - 添加到
targetContents
集合中
SetHandler
实现了NodeHandler接口,<set />
标签的处理器,代码如下:
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 set = new SetSqlNode(configuration, mixedSqlNode);
targetContents.add(set);
}
}
- 继续调用
parseDynamicTags
方法解析<set />
标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象 - 创建
SetSqlNode
对象,该对象继承了TrimSqlNode
,自定义前缀(SET)和需要删除的前缀和后缀(,) - 添加到
targetContents
集合中
ForEachHandler
实现了NodeHandler接口,<foreach />
标签的处理器,代码如下:
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.add(forEachSqlNode);
}
}
- 继续调用
parseDynamicTags
方法解析<foreach />
标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象 - 获得 collection、item、index、open、close、separator 属性
- 根据这些属性创建
ForEachSqlNode
对象 - 添加到
targetContents
集合中
IfHandler
实现了NodeHandler接口,<if />
标签的处理器,代码如下:
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.add(ifSqlNode);
}
}
- 继续调用
parseDynamicTags
方法解析<if />
标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象 - 获得 test 属性
- 根据这个属性创建
IfSqlNode
对象 - 添加到
targetContents
集合中
OtherwiseHandler
实现了NodeHandler接口,<otherwise />
标签的处理器,代码如下:
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.add(mixedSqlNode);
}
}
- 继续调用
parseDynamicTags
方法解析<otherwise />
标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象 - 添加到
targetContents
集合中,需要结合ChooseHandler使用
ChooseHandler
实现了NodeHandler接口,<choose />
标签的处理器,代码如下:
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.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);
}
}
}
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;
}
}
-
先逐步处理
<choose />
标签的<when />
和<otherwise />
子标签们,通过组合 IfHandler 和 OtherwiseHandler 两个处理器,实现对子节点们的解析 -
如果存在
<otherwise />
子标签,则抛出异常 -
根据这些属性创建
ChooseSqlNode
对象 -
添加到
targetContents
集合中
DynamicContext
org.apache.ibatis.scripting.xmltags.DynamicContext
:解析动态SQL语句时的上下文,用于解析SQL时,记录动态SQL处理后的SQL语句,内部提供ContextMap对象保存上下文的参数
构造方法
public class DynamicContext {
/**
* 入参保存在 ContextMap 中的 Key
*
* {@link #bindings}
*/
public static final String PARAMETER_OBJECT_KEY = "_parameter";
/**
* 数据库编号保存在 ContextMap 中的 Key
*
* {@link #bindings}
*/
public static final String DATABASE_ID_KEY = "_databaseId";
static {
// <1.2> 设置 OGNL 的属性访问器
OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
}
/**
* 上下文的参数集合,包含附加参数(通过`<bind />`标签生成的,或者`<foreach />`标签中的集合的元素等等)
*/
private final ContextMap bindings;
/**
* 生成后的 SQL
*/
private final StringJoiner sqlBuilder = new StringJoiner(" ");
/**
* 唯一编号。在 {@link org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.ForEachHandler} 使用
*/
private int uniqueNumber = 0;
public DynamicContext(Configuration configuration, Object parameterObject) {
// <1> 初始化 bindings 参数
if (parameterObject != null && !(parameterObject instanceof Map)) {
// 构建入参的 MetaObject 对象
MetaObject metaObject = configuration.newMetaObject(parameterObject);
// 入参类型是否有对应的类型处理器
boolean existsTypeHandler = configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
bindings = new ContextMap(metaObject, existsTypeHandler);
} else {
bindings = new ContextMap(null, false);
}
// <2> 添加 bindings 的默认值
bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
}
}
类型 | 属性 | 说明 |
---|---|---|
ContextMap | bindings | 上下文的参数集合,包含附加参数(通过<bind /> 标签生成的,或者<foreach /> 标签解析参数保存的),以及几个默认值 |
StringJoiner | sqlBuilder | 保存本次解析后的SQL,每次添加字符串以空格作为分隔符 |
int | uniqueNumber | 唯一编号,在ForEachHandler 处理节点时需要用到,生成唯一数组作为集合中每个元素的索引(作为后缀) |
- 初始化
bindings
参数,创建 ContextMap 对象- 根据入参转换成MetaObject对象
- 在静态代码块中,设置OGNL的属性访问器,OgnlRuntime 是
OGNL
库中的类,设置ContextMap对应的访问器是ContextAccessor
类
- 往
bindings
中添加几个默认值:_parameter
> 入参对象,_databaseId
-> 数据库标识符
ContextMap
DynamicContext的内部静态类,继承HashMap,用于保存解析动态SQL语句时的上下文的参数集合,代码如下:
static class ContextMap extends HashMap<String, Object> {
private static final long serialVersionUID = 2977601501966151582L;
/**
* parameter 对应的 MetaObject 对象
*/
private final MetaObject parameterMetaObject;
/**
* 是否有对应的类型处理器
*/
private final boolean fallbackParameterObject;
public ContextMap(MetaObject parameterMetaObject, boolean fallbackParameterObject) {
this.parameterMetaObject = parameterMetaObject;
this.fallbackParameterObject = fallbackParameterObject;
}
@Override
public Object get(Object key) {
String strKey = (String) key;
if (super.containsKey(strKey)) {
return super.get(strKey);
}
if (parameterMetaObject == null) {
return null;
}
if (fallbackParameterObject && !parameterMetaObject.hasGetter(strKey)) {
return parameterMetaObject.getOriginalObject();
} else {
// issue #61 do not modify the context when reading
return parameterMetaObject.getValue(strKey);
}
}
}
重写了 HashMap 的 get(Object key) 方法,增加支持对 parameterMetaObject
属性的访问
ContextAccessor
DynamicContext的内部静态类,实现 ognl.PropertyAccessor
接口,上下文访问器,代码如下:
static class ContextAccessor implements PropertyAccessor {
@Override
public Object getProperty(Map context, Object target, Object name) {
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) {
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;
}
}
在DynamicContext的静态代码块中,设置OGNL
的属性访问器,设置了ContextMap.class的属性访问器为ContextAccessor
这里方法的入参中的target
,就是 ContextMap 对象
-
在重写的
getProperty
方法中,先从 ContextMap 里面获取属性值(可以回过去看下ContextMap的get方法) -
没有获取到则获取
PARAMETER_OBJECT_KEY
属性的值,如果是 Map 类型,则从这里面获取属性值
回看 DynamicContext 的构造方法,细品一下
标签:初始化,String,private,SqlNode,源码,context,SQL,MyBatis,public 来源: https://www.cnblogs.com/lifullmoon/p/14015066.html