一、Mapper映射文件结构
根据定义Mapper映射文件结构的mybatis-3-mapper.dtd文件,可以知道Mapper映射文件的直接子元素有:
- cache – 对给定命名空间的缓存配置。
- cache-ref – 对其他命名空间缓存配置的引用。
- resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
- parameterMap – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。
- sql – 可被其他语句引用的可重用语句块。
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
mybatis-3-mapper.dtd文件中定义顶级元素的约束如下,包括了直接子元素,根元素的属性等:
<!ELEMENT mapper (cache-ref | cache | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select* )+>
<!ATTLIST mapper
namespace CDATA #IMPLIED
>
二、Mapper文件解析入口
在前面《Mybatis中如何解析所有配置的Mapper映射文件》中已经分析了从Mybatis配置文件中解析<mappers>元素,然后再解析<mapper>或<package>子元素。在解析 <mapper>或<package>子元素的过程中,当加载XML类型的映射时,就会解析每个XML类型映射文件中的所有子元素。
首先是通过XMLMapperBuilder的parse()方法,进入解析每个<mapper>映射文件的方法,代码如下所示:
//XMLMapperBuilder.java
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
在上述的parse()方法中,通过调用configurationElement()方法来处理<mapper>元素中所有的子元素,主要包括了cache-ref 、cache 、 resultMap、 parameterMap、sql、insert、update、delete、select等,首先判断根节点的命名空间,如果为空,直接抛出异常,说明根节点的namespace属性是必须的,然后在通过调用各自的方法,处理前面提到的相应节点。代码如下所示:
//XMLMapperBuilder.java
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
三、缓存(cache、cache-ref )
1、使用
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
基本上就是这样。这个简单语句的效果如下:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
提示: 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
这些属性可以通过 cache 元素的属性来修改。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
- LRU – 最近最少使用:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
- WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
提示: 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
更多缓存相关用法,请参考《官方文档-XML 映射文件-缓存》
2、缓存元素的解析过程
通过上述Mapper映射文件结构可以知道,配置缓存的元素有<cache>、<cache-ref>两种。其中,<cache>子元素用于对给定命名空间的缓存配置;<cache-ref>子元素对其他命名空间缓存配置的引用。
在XMLMapperBuilder的configurationElement()方法中可以知道,解析缓存的方法有cacheRefElement()、cacheElement()两个,分别对应了解析<cache-ref>和<cache>两种元素。
-
<cache>元素结构
通过Mapper文件结构定义文档mybatis-3-mapper.dtd中关于<cache>元素配置结构,可以知道,<cache>元素可以有type、eviction 、flushInterval 、size 、readOnly 、blocking 等属性或者子元素<property>。<!ELEMENT cache (property*)> <!ATTLIST cache type CDATA #IMPLIED eviction CDATA #IMPLIED flushInterval CDATA #IMPLIED size CDATA #IMPLIED readOnly CDATA #IMPLIED blocking CDATA #IMPLIED >
-
<cache>元素解析
解析<cache>元素的逻辑由cacheElement()方法实现。该方法,首先解析了该元素可能有的所有属性值或者子元素<property>,然后通过MapperBuilderAssistant的useNewCache()方法创建对应的缓存Cache实例,并把该缓存实例对象存储到了全局变量configuration的属性caches中。
XMLMapperBuilder的cacheElement()方法,主要实现解析<cache>元素所有属性值或者子元素<property>。
//XMLMapperBuilder.java
private void cacheElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
MapperBuilderAssistant的useNewCache()方法主要实现了根据<cache>元素配置的相关属性,实现创建缓存Cache实例,并把该缓存实例对象存储到了全局变量configuration的属性caches中。
//MapperBuilderAssistant.xml
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
-
<cache-ref>元素结构
通过Mapper文件结构定义文档mybatis-3-mapper.dtd中关于<cache-ref>元素配置结构,可以知道,<cache-ref>元素有且只有一个属性namespace。<!ELEMENT cache-ref EMPTY> <!ATTLIST cache-ref namespace CDATA #REQUIRED >
-
<cache-ref>元素解析
解析<cache-ref>元素的逻辑由cacheRefElement()方法实现。该方法,首先解析了该元素的namespace属性,然后把该元素的namespace属性值和当前命名空间的字符串存储到全局唯一实例对象configuration属性的cacheRefMap中。然后通过CacheRefResolver 实例对象的resolveCacheRef()方法实现元素的解析,在resolveCacheRef()方法中实际上是通过MapperBuilderAssistant对象的useCacheRef()方法实现。
XMLMapperBuilder的cacheRefElement()方法,主要实现解析<cache-ref>元素的namespace属性,然后并创建CacheRefResolver 实例对象,再通过CacheRefResolver 对象的resolveCacheRef()方法实现元素的解析。
//XMLMapperBuilder.java
private void cacheRefElement(XNode context) {
if (context != null) {
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
CacheRefResolver 类的构造函数,还有resolveCacheRef()方法。在resolveCacheRef()方法中,实际上就是通过MapperBuilderAssistant 的useCacheRef()方法解析创建对应缓存对象。
//CacheRefResolver.java
public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
this.assistant = assistant;
this.cacheRefNamespace = cacheRefNamespace;
}
public Cache resolveCacheRef() {
return assistant.useCacheRef(cacheRefNamespace);
}
和解析<cache>元素一样,解析<cache-ref>元素最终是由MapperBuilderAssistant 的useCacheRef()方法实现,这里不会创建新的缓存Cache,而是根据当前的namespace获取已经存在的Cache对象。
public Cache useCacheRef(String namespace) {
if (namespace == null) {
throw new BuilderException("cache-ref element requires a namespace attribute.");
}
try {
unresolvedCacheRef = true;
Cache cache = configuration.getCache(namespace);
if (cache == null) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
}
currentCache = cache;
unresolvedCacheRef = false;
return cache;
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
}
}
四、parameterMap元素解析
已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。
五、resultMap元素解析
resultMap是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。过于复杂,后续单独章节分析该元素的解析过程。
六、sql元素解析
sql元素用来定义可被其他语句引用的可重用语句块。
- SQL元素的结构
通过Mapper文件结构定义文档mybatis-3-mapper.dtd中关于<sql>元素配置结构,可以知道,<sql>元素可以有id、lang、databaseId三个属性,其中id属性是必须;还可以有include、trim、where、set、foreach、choose、if、bind等子元素。
<!ELEMENT sql (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST sql
id CDATA #REQUIRED
lang CDATA #IMPLIED
databaseId CDATA #IMPLIED
>
<!ELEMENT trim (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST trim
prefix CDATA #IMPLIED
prefixOverrides CDATA #IMPLIED
suffix CDATA #IMPLIED
suffixOverrides CDATA #IMPLIED
>
<!ELEMENT where (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ELEMENT set (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ELEMENT foreach (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST foreach
collection CDATA #REQUIRED
item CDATA #IMPLIED
index CDATA #IMPLIED
open CDATA #IMPLIED
close CDATA #IMPLIED
separator CDATA #IMPLIED
>
<!ELEMENT choose (when* , otherwise?)>
<!ELEMENT when (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST when
test CDATA #REQUIRED
>
<!ELEMENT otherwise (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ELEMENT if (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST if
test CDATA #REQUIRED
>
- <sql>解析
解析<sql>元素的逻辑在sqlElement()方法及其重载方法中完成。在解析<sql>过程中,首先判断configuration.getDatabaseId()是否为null,即判断mybatis配置文件中是否配置了<databaseIdProvider>元素。如果配置了<databaseIdProvider>元素,只加载带有databaseId属性,且属性值符合要求的<sql>节点,如果没有配置<databaseIdProvider>元素,就只加载没有带databaseId属性的<sql>元素。
具体过程如下所示:
首先sqlElement()方法及其重载方法,完成了解析<sql>元素的逻辑。其中,在sqlElement(List list)方法中,判断了是否配置了<databaseIdProvider>元素,然后调用重载方法sqlElement(List list, String requiredDatabaseId)。在重载方法中,循环处理每个<sql>元素节点,首先解析节点对应的id,databaseId属性,然后根据databaseIdMatchesCurrent()方法判断节点是否符合要求,如果符合要求就添加到了当前实例的sqlFragments变量中,供后续使用,否则继续处理后续的<sql>节点。
//XMLMapperBuilder.java
private void sqlElement(List<XNode> list) throws Exception {
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
sqlFragments.put(id, context);
}
}
}
XMLMapperBuilder的databaseIdMatchesCurrent()方法,主要用来判断节点是否符合要求,即当前节点的databaseId属性是否和mybatis配置文件中定义的一致,一致的话返回true,当前节点就会被添加到sqlFragments变量中,否则返回false,不添加到sqlFragments变量中。
疑惑:感觉this.sqlFragments.containsKey(id)一直都会返回false,思考中?
//XMLMapperBuilder.java
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
if (requiredDatabaseId != null) {
if (!requiredDatabaseId.equals(databaseId)) {
return false;
}
} else {
if (databaseId != null) {
return false;
}
// skip this fragment if there is a previous one with a not null databaseId
if (this.sqlFragments.containsKey(id)) {
XNode context = this.sqlFragments.get(id);
if (context.getStringAttribute("databaseId") != null) {
return false;
}
}
}
return true;
}
六、select、insert、update、delete元素解析
- select、insert、update、delete元素节点结构
通过Mapper文件结构定义文档mybatis-3-mapper.dtd中关于<select>、<insert>、<update>、<delete>元素配置结构,可以知道其相关结构。
<!ELEMENT select (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST select
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
resultMap CDATA #IMPLIED
resultType CDATA #IMPLIED
resultSetType (FORWARD_ONLY | SCROLL_INSENSITIVE | SCROLL_SENSITIVE | DEFAULT) #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
fetchSize CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true|false) #IMPLIED
useCache (true|false) #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
resultOrdered (true|false) #IMPLIED
resultSets CDATA #IMPLIED
>
<!ELEMENT insert (#PCDATA | selectKey | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST insert
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true|false) #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
keyProperty CDATA #IMPLIED
useGeneratedKeys (true|false) #IMPLIED
keyColumn CDATA #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
>
<!ELEMENT selectKey (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST selectKey
resultType CDATA #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
keyProperty CDATA #IMPLIED
keyColumn CDATA #IMPLIED
order (BEFORE|AFTER) #IMPLIED
databaseId CDATA #IMPLIED
>
<!ELEMENT update (#PCDATA | selectKey | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST update
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true|false) #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
keyProperty CDATA #IMPLIED
useGeneratedKeys (true|false) #IMPLIED
keyColumn CDATA #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
>
<!ELEMENT delete (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST delete
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true|false) #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
>
<!-- Dynamic -->
<!ELEMENT include (property+)?>
<!ATTLIST include
refid CDATA #REQUIRED
>
<!ELEMENT bind EMPTY>
<!ATTLIST bind
name CDATA #REQUIRED
value CDATA #REQUIRED
>
- select、insert、update、delete元素节点解析
和解析<sql>节点类似,首先判断configuration.getDatabaseId()是否为null,即判断mybatis配置文件中是否配置了<databaseIdProvider>元素。然后再调用重载方法buildStatementFromContext(List list, String requiredDatabaseId)进行解析节点,该方法中通过for循环,处理每一个<select>、<insert>、<update>、<delete>元素节点。
//XMLMapperBuilder.java
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
在buildStatementFromContext(List list, String requiredDatabaseId)方法中,又通过XMLStatementBuilder类的parseStatementNode()方法解析每个节点。具体方法如下:
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
在parseStatementNode()方法中,逻辑如下:
首先,根据databaseId判断是否是符合要求的节点元素,如果符合要求继续执行下面逻辑。
其次,解析节点上的相关属性,比如:fetchSize、timeout、parameterType、resultMap、resultType、lang、flushCache、useCache、resultOrdered等属性。
然后,解析节点所属的SqlCommandType类型,即根据节点名称判断是属于下列UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH操作类型中的哪一种。
再使用XMLIncludeTransformer类的applyIncludes()方法解析,其中包含的子节点<include>,该方法通过递归方式,实现所有下级节点的解析过程。
然后,再通过processSelectKeyNodes()方法,实现<selectKey>子节点的解析逻辑。解析该节点的时候,也会根据databaseId判断是否是符合要求的节点。
然后,使用langDriver.createSqlSource()方法创建SqlSource实例对象,SqlSource对象代表了Mapper映射文件或注解中的一个sql语句。
然后,解析resultSets、keyProperty、keyColumn等属性,然后生成对应的KeyGenerator实例对象,该实例对象用于数据主键的生成。
最后,使用MapperBuilderAssistant辅助类的addMappedStatement()方法,生成MappedStatement实例对象,并添加到全局唯一变量configuration的mappedStatements变量中,便于后期使用。
七、结尾
前面已经把Mapper配置文件中除<resultMap >元素之外的其他元素已经分析了,由于<resultMap >元素过于复杂,后续章节单独分析。