mybatis-加载statement配置2
Posted siye1989
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis-加载statement配置2相关的知识,希望对你有一定的参考价值。
1. 概述
本文接 《精尽 MyBatis 源码分析 —— MyBatis 初始化(二)之加载 Mapper 映射配置文件》 一文,来分享 MyBatis 初始化的第三步,加载 Statement 配置。而这个步骤的入口是 XMLStatementBuilder 。下面,我们一起来看看它的代码实现。
在 《精尽 MyBatis 源码分析 —— MyBatis 初始化(二)之加载 Mapper 映射配置文件》 的 「2.3.5 buildStatementFromContext」 中,我们已经看到对 XMLStatementBuilder 的调用代码。代码如下:
// XMLMapperBuilder.java
|
2. XMLStatementBuilder
org.apache.ibatis.builder.xml.XMLStatementBuilder
,继承 BaseBuilder 抽象类,Statement XML 配置构建器,主要负责解析 Statement 配置,即 <select />
、<insert />
、<update />
、<delete />
标签。
2.1 构造方法
// XMLStatementBuilder.java
|
2.2 parseStatementNode
#parseStatementNode()
方法,执行 Statement 解析。代码如下:
// XMLStatementBuilder.java
|
<1>
处,获得id
属性,编号。<2>
处,获得databaseId
属性,并调用#databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId)
方法,判断databaseId
是否匹配。详细解析,见 「2.3 databaseIdMatchesCurrent」 。<3>
处,获得各种属性。<4>
处,调用#getLanguageDriver(String lang)
方法,获得lang
对应的 LanguageDriver 对象。详细解析,见 「2.4 getLanguageDriver」 。<5>
处,获得resultType
对应的类。<6>
处,获得resultSet
对应的枚举值。关于org.apache.ibatis.mapping.ResultSetType
枚举类,点击查看。一般情况下,不会设置该值。它是基于java.sql.ResultSet
结果集的几种模式,感兴趣的话,可以看看 《ResultSet 的 Type 属性》 。<7>
处,获得statementType
对应的枚举值。关于org.apache.ibatis.mapping.StatementType
枚举类,点击查看。<8>
处,获得 SQL 对应的 SqlCommandType 枚举值。<9>
处,获得各种属性。<10>
处,创建 XMLIncludeTransformer 对象,并调用XMLIncludeTransformer#applyIncludes(Node source)
方法,替换<include />
标签相关的内容。详细解析,见 「3. XMLIncludeTransformer」 。<11>
处,调用#processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver)
方法,解析<selectKey />
标签。详细解析,见 「2.5 processSelectKeyNodes」 。<12>
处,调用LanguageDriver#createSqlSource(Configuration configuration, XNode script, Class<?> parameterType)
方法,创建 SqlSource 对象。详细解析,见后续文章。<13>
处,获得 KeyGenerator 对象。分成<13.1>
和<13.2>
两种情况。具体的,胖友耐心看下代码注释。<14>
处,调用MapperBuilderAssistant#addMappedStatement(...)
方法,创建 MappedStatement 对象。详细解析,见 「4.1 addMappedStatement」 中。
2.3 databaseIdMatchesCurrent
#databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId)
方法,判断 databaseId
是否匹配。代码如下:
// XMLStatementBuilder.java
|
- 代码比较简单,胖友自己瞅瞅就得。从逻辑上,和我们在 XMLMapperBuilder 看到的同名方法
#databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId)
方法是一致的。
2.4 getLanguageDriver
#getLanguageDriver(String lang)
方法,获得 lang
对应的 LanguageDriver 对象。代码如下:
// XMLStatementBuilder.java
|
-
调用
MapperBuilderAssistant#getLanguageDriver(lass<? extends LanguageDriver> langClass)
方法,获得 LanguageDriver 对象。代码如下:// MapperBuilderAssistant.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);- 关于
org.apache.ibatis.scripting.LanguageDriverRegistry
类,我们在后续的文章,详细解析。
- 关于
2.5 processSelectKeyNodes
#processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver)
方法,解析 <selectKey />
标签。代码如下:
// XMLStatementBuilder.java
|
<1>
处,获得<selectKey />
节点们。<2>
处,调用#parseSelectKeyNodes(...)
方法,执行解析<selectKey />
节点们。详细解析,见 「2.5.1 parseSelectKeyNodes」 。-
<3>
处,调用#removeSelectKeyNodes(List<XNode> selectKeyNodes)
方法,移除<selectKey />
节点们。代码如下:// XMLStatementBuilder.java
private void removeSelectKeyNodes(List<XNode> selectKeyNodes)
for (XNode nodeToHandle : selectKeyNodes)
nodeToHandle.getParent().getNode().removeChild(nodeToHandle.getNode());
2.5.1 parseSelectKeyNodes
#parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId)
方法,执行解析 <selectKey />
子节点们。代码如下:
// XMLStatementBuilder.java
|
<1>
处,遍历<selectKey />
节点们,逐个处理。<2>
处,获得完整id
编号,格式为$id!selectKey
。这里很重要,最终解析的<selectKey />
节点,会创建成一个 MappedStatement 对象。而该对象的编号,就是id
。<3>
处,获得databaseId
,并调用#databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId)
方法,判断databaseId
是否匹配。?? 通过此处,我们可以看到,即使有多个<selectionKey />
节点,但是最终只会有一个节点被解析,就是符合的databaseId
对应的。因为不同的数据库实现不同,对于获取主键的方式也会不同。<4>
处,调用#parseSelectKeyNode(...)
方法,执行解析单个<selectKey />
节点。详细解析,见 「2.5.2 parseSelectKeyNode」 。
2.5.2 parseSelectKeyNode
#parseSelectKeyNode(...)
方法,执行解析单个 <selectKey />
节点。代码如下:
// XMLStatementBuilder.java
|
<1.1>
处理,获得各种属性和对应的类。<1.2>
处理,创建 MappedStatement 需要用到的默认值。<1.3>
处理,调用LanguageDriver#createSqlSource(Configuration configuration, XNode script, Class<?> parameterType)
方法,创建 SqlSource 对象。详细解析,见后续文章。<1.4>
处理,调用MapperBuilderAssistant#addMappedStatement(...)
方法,创建 MappedStatement 对象。<2.1>
处理,获得 SelectKeyGenerator 的编号,格式为$namespace.$id
。<2.2>
处理,获得 MappedStatement 对象。该对象,实际就是<1.4>
处创建的 MappedStatement 对象。-
<2.3>
处理,调用Configuration#addKeyGenerator(String id, KeyGenerator keyGenerator)
方法,创建 SelectKeyGenerator 对象,并添加到configuration
中。代码如下:// Configuration.java
/**
* KeyGenerator 的映射
*
* KEY:在 @link #mappedStatements 的 KEY 的基础上,跟上 @link SelectKeyGenerator#SELECT_KEY_SUFFIX
*/
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
public void addKeyGenerator(String id, KeyGenerator keyGenerator)
keyGenerators.put(id, keyGenerator);
3. XMLIncludeTransformer
org.apache.ibatis.builder.xml.XMLIncludeTransformer
,XML <include />
标签的转换器,负责将 SQL 中的 <include />
标签转换成对应的 <sql />
的内容。
3.1 构造方法
// XMLIncludeTransformer.java
|
3.2 applyIncludes
#applyIncludes(Node source)
方法,将 <include />
标签,替换成引用的 <sql />
。代码如下:
// XMLIncludeTransformer.java
|
<1>
处,创建variablesContext
,并将configurationVariables
添加到其中。这里的目的是,避免configurationVariables
被下面使用时候,可能被修改。实际上,从下面的实现上,不存在这个情况。<2>
处,调用#applyIncludes(Node source, final Properties variablesContext, boolean included)
方法,处理<include />
。
#applyIncludes(Node source, final Properties variablesContext, boolean included)
方法,使用递归的方式,将 <include />
标签,替换成引用的 <sql />
。代码如下:
// XMLIncludeTransformer.java
|
-
这是个有自递归逻辑的方法,所以理解起来会有点绕,实际上还是蛮简单的。为了更好的解释,我们假设示例如下:
// mybatis-config.xml
<properties>
<property name="cpu" value="16c" />
<property name="target_sql" value="123" />
</properties>
// Mapper.xml
<sql id="123" lang="$cpu">
$cpu
aoteman
qqqq
</sql>
<select id="testForInclude">
SELECT * FROM subject
<include refid="$target_sql" />
</select>
- 方法参数
included
,是否正在处理<include />
标签中。?? 一脸懵逼?不要方,继续往下看。 - 在上述示例的
<select />
节点进入这个方法时,会首先进入<2>
这块逻辑。<2.1>
处,因为 不满足included
条件,初始传入是false
,所以跳过。<2.2>
处,遍历子节点,递归调用#applyIncludes(...)
方法,继续替换。如图所示:- 子节点
[0]
和[2]
,执行该方法时,不满足<1>
、<2>
、<3>
任一一种情况,所以可以忽略。虽然说,满足<3>
的节点类型为Node.TEXT_NODE
,但是included
此时为false
,所以不满足。 - 子节点
[1]
,执行该方法时,满足<1>
的情况,所以走起。
- 子节点
- 在子节点
[1]
,即<include />
节点进入<1>
这块逻辑:<1.1>
处,调用#findSqlFragment(String refid, Properties variables)
方法,获得<sql />
对应的节点,即上述示例看到的,<sql id="123" lang="$cpu"> ... </>
。详细解析,见 「3.3 findSqlFragment」 。<1.2>
处,调用#getVariablesContext(Node node, Properties inheritedVariablesContext)
方法,获得包含<include />
标签内的属性 Properties 对象。详细解析,见 「3.4 getVariablesContext」 。<1.3>
处,递归调用#applyIncludes(...)
方法,继续替换。注意,此处是<sql />
对应的节点,并且included
参数为true
。详细的结果,见 ?????? 处。<1.4>
处,将处理好的<sql />
节点,替换掉<include />
节点。逻辑有丢丢绕,胖友耐心看下注释,好好思考。
- ?????? 在
<sql />
节点,会进入<2>
这块逻辑:<2.1>
处,因为included
为true
,所以能满足这块逻辑,会进行执行。如<sql id="123" lang="$cpu">
的情况,lang
属性是可以被替换的。<2.2>
处,遍历子节点,递归调用#applyIncludes(...)
方法,继续替换。如图所示:- 子节点
[0]
,执行该方法时,满足<3>
的情况,所以可以使用变量 Properteis 对象,进行替换,并修改原节点。
- 子节点
其实,整理一下,逻辑也不会很绕。耐心耐心耐心。
3.3 findSqlFragment
#findSqlFragment(String refid, Properties variables)
方法,获得对应的 <sql />
节点。代码如下:
// XMLIncludeTransformer.java
|
- 比较简单,胖友瞅瞅注释。
3.4 getVariablesContext
#getVariablesContext(Node node, Properties inheritedVariablesContext)
方法,获得包含 <include />
标签内的属性 Properties 对象。代码如下:
// XMLIncludeTransformer.java
|
- 比较简单,胖友瞅瞅注释。
-
如下是
<include />
标签内有属性的示例:<sql id="userColumns"> $alias.id,$alias.username,$alias.password </sql>
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>
4. MapperBuilderAssistant
4.1 addMappedStatement
#addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets)
方法,构建 MappedStatement 对象。代码如下:
// MapperBuilderAssistant.java
|
<1>
处,如果只想的 Cache 未解析,抛出 IncompleteElementException 异常。<2>
处,获得id
编号,格式为$namespace.$id
。<3>
处,创建 MappedStatement.Builder 对象。详细解析,见 「4.1.3 MappedStatement」 。<3.1>
处,调用#getStatementResultMaps(...)
方法,获得 ResultMap 集合。详细解析,见 「4.1.3 getStatementResultMaps」 。<3.2>
处,调用#getStatementParameterMap(...)
方法,获得 ParameterMap ,并设置到 MappedStatement.Builder 中。详细解析,见 4.1.4 getStatementResultMaps」 。
<4>
处,创建 MappedStatement 对象。详细解析,见 「4.1.1 MappedStatement」 。-
<5>
处,调用Configuration#addMappedStatement(statement)
方法,添加到configuration
中。代码如下:// Configuration.java
/**
* MappedStatement 映射
*
* KEY:`$namespace.$id`
*/
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
public void addMappedStatement(MappedStatement ms)
mappedStatements.put(ms.getId(), ms);
4.1.1 MappedStatement
org.apache.ibatis.mapping.MappedStatement
,映射的语句,每个 <select />
、<insert />
、<update />
、<delete />
对应一个 MappedStatement 对象。代码比较简单,但是有点略长,胖友直接点击 链接 查看,已经添加了完整的注释。
另外,比较特殊的是,<selectKey />
解析后,也会对应一个 MappedStatement 对象。
在另外,MappedStatement 有一个非常重要的方法 #getBoundSql(Object parameterObject)
方法,获得 BoundSql 对象。代码如下:
// MappedStatement.java
|
- 需要结合后续文章 《精尽 MyBatis 源码分析 —— SQL 初始化(下)之 SqlSource》 。胖友可以看完那篇文章后,再回过头看这个方法。
4.1.2 ParameterMap
org.apache.ibatis.mapping.ParameterMap
,参数集合,对应 paramType=""
或 paramMap=""
标签属性。代码比较简单,但是有点略长,胖友直接点击 链接 查看,已经添加了完整的注释。
4.1.3 getStatementResultMaps
#getStatementResultMaps(...)
方法,获得 ResultMap 集合。代码如下:
// MapperBuilderAssistant.java
|
- 整体代码比较简单,胖友自己看下。
- 比较奇怪的是,方法参数
resultMap
存在使用逗号分隔的情况。这个出现在使用存储过程的时候,参见 《mybatis调用存储过程返回多个结果集》 。
4.1.4 getStatementResultMaps
#getStatementParameterMap(...)
方法,获得 ParameterMap 对象。代码如下:
// MapperBuilderAssistant.java
|
- 主要看
<1>
处,如果parameterTypeClass
非空,则创建 ParameterMap 对象。 - 关于
<2>
处,MyBatis 官方不建议使用parameterMap
的方式
以上是关于mybatis-加载statement配置2的主要内容,如果未能解决你的问题,请参考以下文章
mybatis-plus Invalid bound statement (not found) 问题