Mybatis3.5.1源码分析
- Mybatis-SqlSessionFactoryBuilder,XMLConfigBuilder,XPathParser源码解析
- Mybatis-Configuration源码解析
- Mybatis-事务对象源码解析
- Mybatis-数据源源码解析
- Mybatis缓存策略源码解析
- Mybatis-DatabaseIdProvider源码解析
- Mybatis-TypeHandler源码解析
- Mybatis-Reflector源码解析
- Mybatis-ObjectFactory,ObjectWrapperFactory源码分析
- Mybatis-Mapper各类标签封装类源码解析
- Mybatis-XMLMapperBuilder,XMLStatmentBuilder源码分析
- Mybatis-MapperAnnotationBuilder源码分析
- [Mybatis-MetaObject,MetaClass源码解析]https://www.jianshu.com/p/f51fa552f30a)
- Mybatis-LanguageDriver源码解析
- Mybatis-SqlSource源码解析
- Mybatis-SqlNode源码解析
- Mybatis-KeyGenerator源码解析
- Mybatis-Executor源码解析
- Mybatis-ParameterHandler源码解析
- Mybatis-StatementHandler源码解析
- Mybatis-DefaultResultSetHandler(一)源码解析
- Mybatis-DefaultResultSetHandler(二)源码解析
- Mybatis-ResultHandler,Cursor,RowBounds 源码分析
- Mybatis-MapperProxy源码解析
- Mybatis-SqlSession源码解析
- Mybatis-Interceptor源码解析
XMLMapperBuilder
/**
* Copyright 2009-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.builder.xml;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.ibatis.builder.BaseBuilder;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.CacheRefResolver;
import org.apache.ibatis.builder.IncompleteElementException;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.ResultMapResolver;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Discriminator;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
/**
*
* 参考博客:
*
* - https://www.jianshu.com/p/7f61d19ef83d
* - https://blog.csdn.net/abcd898989/article/details/51189977
*
*
* @author Clinton Begin
* @author Kazuki Shimizu
*/
public class XMLMapperBuilder extends BaseBuilder {
/**
* XML解析器
*/
private final XPathParser parser;
/**
* 构建映射器助理
*/
private final MapperBuilderAssistant builderAssistant;
/**
* SQL碎片
*/
private final Map sqlFragments;
/**
* 资源路径
*/
private final String resource;
@Deprecated
public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map sqlFragments, String namespace) {
this(reader, configuration, resource, sqlFragments);
this.builderAssistant.setCurrentNamespace(namespace);
}
@Deprecated
public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map sqlFragments) {
this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
/**
* 除了调用{@link #XMLMapperBuilder(InputStream, Configuration, String, Map)},
* 还对{@link #builderAssistant}设置当前命名空间
*/
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map sqlFragments, String namespace) {
this(inputStream, configuration, resource, sqlFragments);
//命名空间就是包+类名,
// namespace如果为null,或者 当前{@link #currentNamespace}已经有值而且传进来的{@link @currentNamespace}
// 和{@link #currentNamespace}不一样都会导致抛出{@link BuilderException}
this.builderAssistant.setCurrentNamespace(namespace);
}
/**
* 会通过将{@link @inputStream},{@link Configuration#getVariables()}构建出{@link XPathParser},
* 传入{@link #XMLMapperBuilder(XPathParser, Configuration, String, Map)}
*/
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map sqlFragments) {
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
/**
* 真正初始化的构造方法
*/
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map sqlFragments) {
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
/**
* 解析Mapper.xml,解析mapper标签下的所有标签,并对解析出来的标签信息加以封装,
* 然后添加到Mybatis全局配置信息中。然后重新解析Mybatis全局配置信息中未能完成解析的
* ResultMap标签信息,CacheRef标签信息,DML标签信息
*/
public void parse() {
//如果没有加载过resource
if (!configuration.isResourceLoaded(resource)) {
//解析mapper标签下的所有标签,并对解析出来的标签信息加以封装,然后添加到Mybatis全局配置信息中
configurationElement(parser.evalNode("/mapper"));
//将resouce添加到Mybatis全局配置信息中,以防止重新加载对应的resource
configuration.addLoadedResource(resource);
//找出对应当前Mapper.xml的java类型,并添加到Mybatis全局配置信息中
bindMapperForNamespace();
}
//重新解析Mybatis全局配置信息中未能完成解析的ResultMap标签信息
parsePendingResultMaps();
//重新解析Mybatis全局配置信息中未能完成解析的CacheRef标签信息
parsePendingCacheRefs();
//重新解析Mybatis全局配置信息中未能完成解析的DML标签信息
parsePendingStatements();
}
public XNode getSqlFragment(String refid) {
return sqlFragments.get(refid);
}
/**
* 解析mapper标签下的所有标签,并对解析出来的标签信息加以封装,然后添加到Mybatis全局配置信息中
* @param context mapper标签
*/
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);
//解析 cache-ref 标签
cacheRefElement(context.evalNode("cache-ref"));
//解析 cache 标签
cacheElement(context.evalNode("cache"));
//解析 paramterMap 标签
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析 resultMap 表
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql标签
sqlElement(context.evalNodes("/mapper/sql"));
//构建每个DML标签对应的XMLStatmentBuilder,并通过XMLStatementBuilder对DML标签进行解析
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);
}
}
/**
* 构建每个DML标签对应的XMLStatmentBuilder,并通过XMLStatementBuilder对DML标签
* 进行解析
* @param list DML标签集合(select|insert|update|delete标签)
*/
private void buildStatementFromContext(List list) {
//先构建对应mybatis全局配置信息的databaseId的Statement
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
//mybatis认为DML标签没有设置databaseId属性都是对应的mybatis全局配置信息的databaseId
buildStatementFromContext(list, null);
}
/**
* 构建每个DML标签对应的XMLStatmentBuilder,并通过XMLStatementBuilder对DML标签
* 进行解析
* @param list DML标签集合(select|insert|update|delete标签)
* @param requiredDatabaseId mybatis全局配置信息的databaseId
*/
private void buildStatementFromContext(List list, String requiredDatabaseId) {
//遍历DML标签集合
for (XNode context : list) {
//对每个DML标签新建一个XML Statement 构建器
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//解析DML标签,封装成MappedStatement对象,添加到Mybatis全局配置信息中
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
//发生未能完成解析异常时,将XML Statment构建器添加到Mybatis全局配置信息中,后期再尝试解析。
configuration.addIncompleteStatement(statementParser);
}
}
}
/**
* 重新解析Mybatis全局配置信息中未能完成的ResultMap标签信息
*/
private void parsePendingResultMaps() {
//获取Mybatis全局配置信息中未能完成的ResultMap标签信息
Collection incompleteResultMaps = configuration.getIncompleteResultMaps();
//同步,防止重新解析
synchronized (incompleteResultMaps) {
//获取incompleteResultMaps的迭代器
Iterator iter = incompleteResultMaps.iterator();
//遍历incompleteResultMaps
while (iter.hasNext()) {
try {
//获取ResultMap标签信息解析器重新解析
iter.next().resolve();
//如果解析成功,就从集合中移除
iter.remove();
} catch (IncompleteElementException e) {
//抛出异常时,不做任何操作,让未能完成的ResultMap标签信息继续在Mybatis全局配置信息中等待下次解析
// ResultMap is still missing a resource... ResultMap标签仍然丢失一个资源
}
}
}
}
/**
* 重新解析Mybatis全局配置信息中未能完成解析的CacheRef标签信息
*/
private void parsePendingCacheRefs() {
//获取Mybatis全局配置信息中未能完成解析的CacheRef标签信息
Collection incompleteCacheRefs = configuration.getIncompleteCacheRefs();
//同步,防止重新解析
synchronized (incompleteCacheRefs) {
//获取incompleteCacheRefs的迭代器
Iterator iter = incompleteCacheRefs.iterator();
//遍历incompleteCacheRefs
while (iter.hasNext()) {
try {
//获取CacheRef标签信息解析器重新解析
iter.next().resolveCacheRef();
//如果解析成功,就从集合中移除
iter.remove();
} catch (IncompleteElementException e) {
//抛出异常时,不做任何操作,让未能完成解析的CacheRef标签信息继续在Mybatis全局配置信息中等待下次解析
// Cache ref is still missing a resource... CacheRef标签仍然丢失一个资源
}
}
}
}
/**
* 重新解析Mybatis全局配置信息中未能完成解析的DML标签信息
*/
private void parsePendingStatements() {
//获取Mybatis全局配置信息中未能完成解析的DML标签信息
Collection incompleteStatements = configuration.getIncompleteStatements();
//同步,防止重新解析
synchronized (incompleteStatements) {
//获取incompleteStatements的迭代器
Iterator iter = incompleteStatements.iterator();
//遍历incompleteStatements
while (iter.hasNext()) {
try {
//获取DML标签信息解析器重新解析
iter.next().parseStatementNode();
//如果解析成功,就从集合中移除
iter.remove();
} catch (IncompleteElementException e) {
//抛出异常时,不做任何操作,让未能完成解析的DML标签信息继续在Mybatis全局配置信息中等待下次解析
// Statement is still missing a resource... DML标签仍然丢失一个资源
}
}
}
}
/**
* cache-ref 标签解析
*
* cache-ref标签作用:若想在命名空间中共享相同的缓存配置和实例,可以使用 cache-ref 元素来引用另外一个缓存。
*
*
* 新建一个{@link CacheRefResolver}实例,并通过{@link CacheRefResolver}实例获取CacheRef,因为在
* 因为该方法已经将找到的{@link Cache}实例赋值到{@link #builderAssistant}的{@link MapperBuilderAssistant#currentCache}中,
* 也会更新{@link #builderAssistant}的@link MapperBuilderAssistant#unresolvedCacheRef}为false.
*
*
* 获取更新CachRef期间抛出的{@link IncompleteElementException}都会被处理调用,并将{@link CacheRefResolver}实例
* 添加{@link #configuration}的{@link Configuration#incompleteCacheRefs}中
*
* @param context
*/
private void cacheRefElement(XNode context) {
if (context != null) {
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
//解析缓存引用CacheRef
/**
* 因为该方法已经将找到的{@link Cache}实例赋值到{@link #builderAssistant}
* 的{@link MapperBuilderAssistant#currentCache}中,也会更新{@link #builderAssistant}的
* {@link MapperBuilderAssistant#unresolvedCacheRef}为false.
*/
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
//添加解析CacheRef失败的{@link CacheRefResolver},推测是为了统计、报告 TODO 推测
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
/**
* cache标签解析
*
* cache作用:给定命名空间的缓存配置。
*
*
* 从Mapper.xml文件中获取Cache标签的配置信息,并根据配置的信息构建Cache的实例对象。
*
*
* 参考博客:https://blog.csdn.net/qq_28061489/article/details/79329095
*
*/
private void cacheElement(XNode context) {
if (context != null) {
//type:指定自定义缓存的全类名(实现Cache接口即可),PERPETUAL是别名,对应PerpetualCache缓存类
String type = context.getStringAttribute("type", "PERPETUAL");
Class extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
/**
* eviction:缓存的回收策略
* LRU - 最近最少使用,移除最长时间不被使用的对象,对应实现类:LruCache
* FIFO - 先进先出,按对象进入缓存的顺序来移除它们,对应实现类:FifoCache
* SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象,对应实现类:LruCache
* WEAK - 弱引用,更积极地移除基于垃圾收集器和弱引用规则的对象,对应实现类:WeakCache
*/
String eviction = context.getStringAttribute("eviction", "LRU");
Class extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
//缓存刷新间隔,缓存多长时间清空一次,默认不清空,设置一个毫秒值,只有
Long flushInterval = context.getLongAttribute("flushInterval");
//size:缓存存放多少个元素
Integer size = context.getIntAttribute("size");
/**
* readOnly:是否只读
* true:只读:mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
* false:读写(默认):mybatis觉得获取的数据可能会被修改
*/
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
//若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
//构建Cache实例
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
/**
* 解析ParameterMap标签,构建ParameterMap实例对象,并添加到{@link #configuration}中
*/
private void parameterMapElement(List list) {
for (XNode parameterMapNode : list) {
//ParameterMap标签的ID属性
String id = parameterMapNode.getStringAttribute("id");
//ParameterMap标签的type属性
String type = parameterMapNode.getStringAttribute("type");
//获取对应的类
Class> parameterClass = resolveClass(type);
//获取parameterMap下面的子标签 parameter
List parameterNodes = parameterMapNode.evalNodes("parameter");
List parameterMappings = new ArrayList<>();
for (XNode parameterNode : parameterNodes) {
String property = parameterNode.getStringAttribute("property");
String javaType = parameterNode.getStringAttribute("javaType");
String jdbcType = parameterNode.getStringAttribute("jdbcType");
//属性对应的结果映射
String resultMap = parameterNode.getStringAttribute("resultMap");
/**
* mode:当使用存储过程时,需要设置一个参数 mode,其值有 IN(输入参数)、OUT(输出参数)、INOUT(输入 / 输出参数)。
* 一个存储过程可以有多个 IN 参数,至多有一个 OUT 或 INOUT 参数
*/
String mode = parameterNode.getStringAttribute("mode");
//类型接收器
String typeHandler = parameterNode.getStringAttribute("typeHandler");
//numericScale表示小数点保留的位数
Integer numericScale = parameterNode.getIntAttribute("numericScale");
ParameterMode modeEnum = resolveParameterMode(mode);
//属性对应的java类型
Class> javaTypeClass = resolveClass(javaType);
//属性对应的jdbc类型
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
//属性对应的TypeHandler实现类
Class extends TypeHandler>> typeHandlerClass = resolveClass(typeHandler);
// 构建ParameterMapping,一个属性一个ParameterMapping
ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
parameterMappings.add(parameterMapping);
}
//构建ParameterMap,并添加到{@link #configuration}中
builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
}
}
/**
* 解析resultMap标签(处理多个)
*/
private void resultMapElements(List list) throws Exception {
//循环调用reusltMapElemet(XNode)方法
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried 忽略,它会被重试。
}
}
}
/**
* 解析resultMap标签(处理单个)
*/
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.emptyList(), null);
}
/**
* 解析resultMap标签,构建resultMap实例
* @param resultMapNode resultMap节点
* @param additionalResultMappings 额外的resultMapping
* @param enclosingType 有可能是reusltMap对应的javaType
* @return 构建成功的ResultMap实例
*/
private ResultMap resultMapElement(XNode resultMapNode, List additionalResultMappings, Class> enclosingType) throws Exception {
/**
* {@link XNode#getValueBasedIdentifier}就是mybatis给XNode的一个标识符。
* 如:Mapper的ResultMap => Mapper[XXX]_ResultMap[XXX](XXX:显示id的属性值,获取不到就获取value的属性值,
*/
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//ResultMap对应的javaType的包名+类名,从这里可以看出,有四个属性都可以指定javaType,优先级为:type=>ofType=>resultType=>javaType
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
//ResultMap对应的javaType
Class> typeClass = resolveClass(type);
if (typeClass == null) {
//针对未定义返回类型的元素的返回值类型解析,看看 association 和 case 元素没有显式地指定返回值类型
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
List resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
// 加载子元素
List resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
//处理constructor标签
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
//处理discriminator标签
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
//处理id标签
List flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
//Id,没有设置mybatis就会自动配置一个
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//继承
String extend = resultMapNode.getStringAttribute("extends");
//自动映射
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
//构建{@link ResultMap}实例,并添加到{@link #configuration}
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
//添加构建ResultMap时抛出异常的resultMapResolver实例
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
/**
* 针对未定义返回类型的元素的返回值类型解析,看看 association 和 case 元素没有显式地指定返回值类型
* @param resultMapNode 标签节点
* @param enclosingType
* @return
*/
protected Class> inheritEnclosingType(XNode resultMapNode, Class> enclosingType) {
//association标签是用于配置非简单类型的映射关系
if ("association".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
String property = resultMapNode.getStringAttribute("property");
if (property != null && enclosingType != null) {
// 根据反射信息确定字段的类型,从代码中可以看出用的是proptery对应的setter方法的参数类型。
MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
return metaResultType.getSetterType(property);
}
} else if ("case".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
// case 元素返回值属性与 resultMap 父元素相同
return enclosingType;
}
return null;
}
/**
* 处理Constructor标签
*
*
* constructor元素 ,指定使用指定参数列表的构造函数来实例化领域模型。注意:其子元素顺序必须与参数列表顺序对应.
* idArg子元素 ,标记该入参为主键
* arg子元素 ,标记该入参为普通字段(主键使用该子元素设置也是可以的)
* 参考博客:https://www.cnblogs.com/fsjohnhuang/p/4076592.html
*
* @param resultChild 子节点
* @param resultType resultMap的java类型
* @param resultMappings resultMap的resultMapping集合
* @throws Exception
*/
private void processConstructorElement(XNode resultChild, Class> resultType, List resultMappings) throws Exception {
List argChildren = resultChild.getChildren();
for (XNode argChild : argChildren) {
//每个标签都会有一个flags列表
List flags = new ArrayList<>();
flags.add(ResultFlag.CONSTRUCTOR);
if ("idArg".equals(argChild.getName())) {//如果是idArg标签,{@code flag}还会多一个{@link ResultFlag.ID}表明该属性是一个主键
flags.add(ResultFlag.ID);
}
//每个子标签都会封装成ResultMapping并添加到resultMappings
resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
}
}
/**
* 处理discriminator标签
* @param context discriminator标签节点
* @param resultType ResutMap标签的java类型
* @param resultMappings {@code context}的ResutMap标签的属性标签封装类集合
* @return
* @throws Exception
*/
private Discriminator processDiscriminatorElement(XNode context, Class> resultType, List resultMappings) throws Exception {
//列名
String column = context.getStringAttribute("column");
//java类(别名或者包名+类名)
String javaType = context.getStringAttribute("javaType");
//jdbc类型
String jdbcType = context.getStringAttribute("jdbcType");
//类型处理器(别名或者包名+类名)
String typeHandler = context.getStringAttribute("typeHandler");
//java类
Class> javaTypeClass = resolveClass(javaType);
//类型处理器类
Class extends TypeHandler>> typeHandlerClass = resolveClass(typeHandler);
//jdbc类型枚举
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// 解析 discriminator 的 case 子元素
Map discriminatorMap = new HashMap<>();
for (XNode caseChild : context.getChildren()) {
String value = caseChild.getStringAttribute("value");
// 解析不同列值对应的不同 resultMap
String resultMap = caseChild.getStringAttribute("resultMap",
processNestedResultMappings(caseChild, resultMappings, resultType));
discriminatorMap.put(value, resultMap);
}
return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}
/**
* 解析Sql标签
*
* 参考博客:https://segmentfault.com/a/1190000017378490
*
* @param list sql标签元素
*/
private void sqlElement(List list) {
//第一次用于处理 databaseId 与全局 Configuration 实例的 databaseId 一致的节点
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
/**
* 第二次用于处理节点的 databaseId 为 null 的情况,针对同一个 id ,
* 优先选择存在 databaseId 并且与数据源的一致。
*/
sqlElement(list, null);
}
/**
* 解析Sql标签
* @param list sql标签元素
* @param requiredDatabaseId 当前项目引用的dataBaseId
*/
private void sqlElement(List list, String requiredDatabaseId) {
for (XNode context : list) {
//对应的数据库Id
String databaseId = context.getStringAttribute("databaseId");
//ID
String id = context.getStringAttribute("id");
//检查ID是否简写,简写就应用当前命名空间
id = builderAssistant.applyCurrentNamespace(id, false);
//找出匹配当前配置的数据库Id的sql标签元素,然后加入sqlFragments
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
sqlFragments.put(id, context);
}
}
}
/**
* 匹配当前配置的数据库Id
* @param id SQL标签ID属性
* @param databaseId SQL标签databaseId属性
* @param requiredDatabaseId 当前项目引用的dataBaseId
*/
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
//当前项目引用的dataBaseId是否等于当前项目引用的dataBaseId
if (requiredDatabaseId != null) {
return requiredDatabaseId.equals(databaseId);
}
/**
* 到这里requiredDatabaseId就为null,也因为不设置databaseId默认就是对应当前项目引用的dataBaseId,
* 所以就算dataBaseId有设置,当前项目引用的dataBaseId没有设置,
* 就相当于一个有值的字符串判断一个空字符串一个道理,所以认为是不匹配的。
*/
if (databaseId != null) {
return false;
}
/**
* 没有加入sqlFragments,统一返回true。因为到这里databaseId就是为null的情况,
* 也因为不设置databaseId默认就是对应当前项目引用的dataBaseId,就相当于一个空字符串判断一个空字符串一样,
* 所以认为是匹配的。
*/
if (!this.sqlFragments.containsKey(id)) {
return true;
}
/**
* 下面代码触发条件是:databaseId为null,使得databaseId不等于requiredDatabaseId,id已经存在sqlFargment中。
* 如果我没有推测错误的话,mybatis认为没有设置databaseId的sql标签,是不够明确认为这个sql标签就是
* 对应于当前项目引用的dataBaseId,所以当出现多个相同id的sql标签时,mybatis会以最后一个作为真正
* 对应于当前项目引用的dataBaseId的sql标签,所以这里如果上一个sql标签没有设置databaseId话,
* 会覆盖掉上一个sql标签。
*/
// skip this fragment if there is a previous one with a not null databaseId
//如果它是上一个有databaseId的fragment就跳过
XNode context = this.sqlFragments.get(id);
return context.getStringAttribute("databaseId") == null;
}
/**
* 根据{@code context}中构建{@link ResultMapping}
* @param context 标签节点信息
* @param resultType resultMap标签对应的javaType
* @param flags 标签标记,表明{@code context}是个构造函数的参数 {@link ResultFlag#CONSTRUCTOR} ,还是一个ID {@link ResultFlag#ID}
* @return
* @throws Exception
*/
private ResultMapping buildResultMappingFromContext(XNode context, Class> resultType, List flags) throws Exception {
//获取属性名
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {//如果是标签下的标签,就会有ResultFlag.CONSTRUCTOR标识
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
//列名
String column = context.getStringAttribute("column");
// java 类型
String javaType = context.getStringAttribute("javaType");
// jdbc 类型
String jdbcType = context.getStringAttribute("jdbcType");
// 嵌套的 select id
String nestedSelect = context.getStringAttribute("select");
// 获取嵌套的 resultMap id
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.emptyList(), resultType));
// 获取指定的不为空才创建实例的列
String notNullColumn = context.getStringAttribute("notNullColumn");
// 列前缀,columnPrefix用法:https://www.cnblogs.com/circlebreak/p/6647710.html
String columnPrefix = context.getStringAttribute("columnPrefix");
// 类型转换器
String typeHandler = context.getStringAttribute("typeHandler");
//集合的多结果集
String resultSet = context.getStringAttribute("resultSet");
//指定外键对应的列名
String foreignColumn = context.getStringAttribute("foreignColumn");
//指定外键对应的列名,优先看当前标签的属性设置,没有才看全局配置configuration
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
//加载返回值类型
Class> javaTypeClass = resolveClass(javaType);
// 加载类型转换器类型
Class extends TypeHandler>> typeHandlerClass = resolveClass(typeHandler);
// 加载 jdbc 类型对象
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
/**
* 处理嵌套的ResultMappings
*
* 该方法只会处理association,collection,case标签
*
*
* 如果指定了 select 属性,则映射时只需要获取对应 select 语句的 resultMap;
* 如果未指定,则需要重新调用 {@link #resultMapElement(XNode, List, Class)}
* 构建resultMap实例,并返回resultMap实例的Id
*
*
* @param context 节点
* @param resultMappings resultMap的resultMapping集合
* @param enclosingType resultMap对应的javaType
* @return resultMap实例的Id
*/
private String processNestedResultMappings(XNode context, List resultMappings, Class> enclosingType) throws Exception {
if ("association".equals(context.getName())
|| "collection".equals(context.getName())
|| "case".equals(context.getName())) {
if (context.getStringAttribute("select") == null) {
// 如果是 association、collection 或 case 元素并且没有 select 属性
// collection 元素没有指定 resultMap 或 javaType 属性,
// 需要验证 resultMap 父元素对应的返回值类型是否有对当前集合的赋值入口
validateCollection(context, enclosingType);
ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
return resultMap.getId();
}
}
return null;
}
/**
* 验证集合
*
* 在context为collection标签且没有设置resultMap和javaType的时候,尝试从enclosingType的反射信息中判断有没有context的property的属性的setter方法。
* 没有就抛出异常{@link BuilderException}。
*
* @param context 节点
* @param enclosingType resultMap的javaType
*/
protected void validateCollection(XNode context, Class> enclosingType) {
//在context为列表标签且没有设置resultMap和javaType
if ("collection".equals(context.getName()) && context.getStringAttribute("resultMap") == null
&& context.getStringAttribute("javaType") == null) {
MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
String property = context.getStringAttribute("property");
//尝试从enclosingType的反射信息中判断context的property的属性的setter方法。
if (!metaResultType.hasSetter(property)) {
throw new BuilderException(
"Ambiguous collection type for property '" + property + "'. You must specify 'javaType' or 'resultMap'.");
}
}
}
/**
* 找出对应当前Mapper.xml的java类型,并添加到Mybatis全局配置信息中
*/
private void bindMapperForNamespace() {
//获取当前命名空间
String namespace = builderAssistant.getCurrentNamespace();
//如果当前命名空间不为null
if (namespace != null) {
Class> boundType = null;
try {
//获取Mapper.xml需要绑定java类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required 因为绑定的java类型并不是必须的,所有忽略
}
//如果绑定类型不为null
if (boundType != null) {
//如果Mybatis全局配置信息中没有注册boundType
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
// 译:
// Spring框架可能不知道真正的资源名称,所以我们设置一个标签
// 去防止再次加载这个资源映射接口
// 查看MapperAnnotationBuilder#loadXmlResource
//将{@code resource} 添加到mybatis全局配置信息的已加载资源集合里
configuration.addLoadedResource("namespace:" + namespace);
//将boundType加入到mybatis全局配置信息的mapperRegistry中
configuration.addMapper(boundType);
}
}
}
}
}
MapperBuilderAssistant
/**
* Copyright 2009-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.builder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.decorators.LruCache;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.mapping.CacheBuilder;
import org.apache.ibatis.mapping.Discriminator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMap;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.mapping.ResultSetType;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
/**
* 构建映射器助理
*
* 参考博客:https://www.jb51.net/article/85669.htm
*
* @author Clinton Begin
*/
public class MapperBuilderAssistant extends BaseBuilder {
/**
* 当前命名空间
*/
private String currentNamespace;
/**
* 类的java文件相等路径。如:'bin/study/mapper/IUser.java (best guess)'
*
* 资源路径,当前项目,有可能是java文件也有可能是.xml文件
*/
private final String resource;
/**
* 当前Cache实例,在构建MapperStatement对象时,会将currentCache对象传进去
*/
private Cache currentCache;
/**
* 未成功解析CacheRef标记
*/
private boolean unresolvedCacheRef; // issue #676
/**
*
* @param configuration mybatis全局配置信息
* @param resource 资源路径,当前项目,有可能是java文件也有可能是.xml文件
*/
public MapperBuilderAssistant(Configuration configuration, String resource) {
super(configuration);
ErrorContext.instance().resource(resource);
this.resource = resource;
}
/**
* 获取当前命名空间
*/
public String getCurrentNamespace() {
return currentNamespace;
}
/**
* 设置当前命名空间
*
* {@link @currentNamespace}如果为null,
*
* @param currentNamespace 如果为null,或者 当前{@link #currentNamespace}已经有值而且传进来的{@link @currentNamespace}
* 和{@link #currentNamespace}不一样都会导致抛出{@link BuilderException}
*/
public void setCurrentNamespace(String currentNamespace) {
if (currentNamespace == null) {
throw new BuilderException("The mapper element requires a namespace attribute to be specified.");
}
//后面的这个等于看起来有点对于,我推测它存在的意义是为了抛出异常的时候能够更加清晰的描述给用户。
if (this.currentNamespace != null && !this.currentNamespace.equals(currentNamespace)) {
throw new BuilderException("Wrong namespace. Expected '"
+ this.currentNamespace + "' but found '" + currentNamespace + "'.");
}
this.currentNamespace = currentNamespace;
}
/**
* 检查ID是否简写,简写就应用当前命名空间;
* 检查便签ID{@link @base}的语法,并返回合法的标签ID.
*
* 如果{@link @base}是引用别的命名空间声明的,就判断base是否已经声明了命名空间,声明了就直接返回base,没有声明的默认给base的开头加上当前的命名空间
* 后再返回
*
*
* 如果{@link @base}是引用当前的命名空间声明的,就判断base的开头是否是当前命名空间,是就直接返回,不是就再判断是否引用了其他的命名空间,是就抛出异常,
* 不是,就认为base只是省略了当前的命名空间,然后mybatis默认给base的开头加上当前的命名空间后再返回
*
* @param base 标签ID
* @param isReference 是否可能是引用别的命名空间声明,true为是,false为否
* @return
*/
public String applyCurrentNamespace(String base, boolean isReference) {
if (base == null) {
return null;
}
if (isReference) {
// is it qualified with any namespace yet? 与任何名称空间是合格的吗?
//因为已经加上了命名空间,所以只需要检查是否带有'.',即base的语法检查。这里不会检查到base是否存在配置项中,因为base有可能是其他mapper的声明的。
if (base.contains(".")) {
return base;
}
} else {
// is it qualified with this namespace yet?
//因为引用的当前命名空间,所以要检查是否是以当前命名空间开头
if (base.startsWith(currentNamespace + ".")) {
return base;
}
//如果是引用当前命名空间,但是又没有引用了其他的命名空间,这是不允许的。
if (base.contains(".")) {
throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
}
}
//到这一步,就认识是:引用的是当前命名空间,但是又省略加上当前命名空间作为开头的base。
//这种情况就要mybatis会自动加上命名空间+'.'
return currentNamespace + "." + base;
}
/**
* CacheRef对应的Cache对象,会从Mybatis全局配信息中获取 {@code namespace} 对应的Cache对象,
* 如果没有找到,会抛出IncompleteElementException异常,找到会将Cache对象赋值给
* currentCache,在构建MapperStatement对象时,将currentCache传进去
* @param namespace 如果为null,抛出{@link BuilderException}
* @return
*/
public Cache useCacheRef(String namespace) {
if (namespace == null) {
throw new BuilderException("cache-ref element requires a namespace attribute.");
}
try {
//未解析缓存引用标记,在成功获取到CacheRef对应的缓存后会设置成false
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);
}
}
/**
* 构建一个缓存实例
*
* 通过{@link CacheBuilder}构建出Cache实例对象,并将Cache实例对象添加到{@link #configuration},并赋值到
* {@link #currentCache}
*
* @param typeClass cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
* @param evictionClass 定义回收的策略,常见的有FIFO,LRU
* @param flushInterval 配置一定时间自动刷新缓存,单位是毫秒
* @param size 最多缓存对象的个数
* @param readWrite 是否只读,若配置可读写,则需要对应的实体类能够序列化。
* @param blocking 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
* @param props 自定义的属性,供给自定义的Cache实现类
*/
public Cache useNewCache(Class extends Cache> typeClass,
Class extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
//构建Cache实例对象,构建出来的Cache实例是经过一层又一层装饰的装饰类
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();
//添加Cache实例
configuration.addCache(cache);
currentCache = cache;
return cache;
}
/**
* 构建ParameterMap,并添加到{@link #configuration}中
* @param id ParameterMap标签的ID属性
* @param parameterClass ParamterMap标签的type属性,即参数类型
* @param parameterMappings ParamterMap标签的所有paramter标签,即参数的所有属性列表
*/
public ParameterMap addParameterMap(String id, Class> parameterClass, List parameterMappings) {
//将id带上currentNamespace前缀,如:id='user',currentNamespace='bin.study.mapper' ==> id='bin.study.mapper.user'
id = applyCurrentNamespace(id, false);
// 构建ParameterMap
ParameterMap parameterMap = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings).build();
//// configuration添加parameterMap
configuration.addParameterMap(parameterMap);
return parameterMap;
}
/**
* 构造ParameterMapper
* @param parameterType 参数java类型
* @param property 参数的属性名
* @param javaType 参数的属性类型
* @param jdbcType 参数的属性对应的jdbcType
* @param resultMap 参数的属性对应的结果映射
* @param parameterMode 参数的属性,当使用存储过程时,需要设置一个参数 mode,其值有 IN(输入参数)、OUT(输出参数)、INOUT(输入 / 输出参数)。
* @param typeHandler 参数的属性对应的TypeHandler
* @param numericScale 参数的属性的小数点保留的位数
* @return
*/
public ParameterMapping buildParameterMapping(
Class> parameterType,
String property,
Class> javaType,
JdbcType jdbcType,
String resultMap,
ParameterMode parameterMode,
Class extends TypeHandler>> typeHandler,
Integer numericScale) {
// resultMap如果已经有命名空间作为前缀,经过下面代码将不会出现改变,但如果没有命名空间,就会加上当前命名空间作为前缀
resultMap = applyCurrentNamespace(resultMap, true);
// Class parameterType = parameterMapBuilder.type();
//解析获取参数或者结果的属性的java类型
Class> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType);
// 获取接收javaTypeClass的TypeHandler实例
TypeHandler> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
return new ParameterMapping.Builder(configuration, property, javaTypeClass)
.jdbcType(jdbcType)
.resultMapId(resultMap)
.mode(parameterMode)
.numericScale(numericScale)
.typeHandler(typeHandlerInstance)
.build();
}
/**
* 构建{@link ResultMap}实例,并添加到{@link #configuration}
* @param id ResultMap标签的ID
* @param type ResultMap对应的java类型
* @param extend 继承的ResultMap,这里是继承的ResultMap的Id
* @param discriminator 鉴别器
* @param resultMappings ResultMap的子标签结果映射
* @param autoMapping 是否自动映射
*/
public ResultMap addResultMap(
String id,
Class> type,
String extend,
Discriminator discriminator,
List resultMappings,
Boolean autoMapping) {
//验证并构建正确的Id命名
id = applyCurrentNamespace(id, false);
//验证并构建正确的Id命名
extend = applyCurrentNamespace(extend, true);
if (extend != null) {
//extend的ReusltMap必须必当前ResultMap先注册。
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
ResultMap resultMap = configuration.getResultMap(extend);
/**
* 复制多一个extendReusltMap的ResultMapping,因为出于保护原数据的考虑,
* resultMap的中的ResultMapping是不能更改的。
*/
List extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
//删除被重写了的ResultMapping
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
//删除父类构造方法如果子类声明了构造方法
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
}
//将继承的resultMapping放进reusltMappings中
resultMappings.addAll(extendedResultMappings);
}
//构建resultMap实例
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
//添加reusltMap实例
configuration.addResultMap(resultMap);
return resultMap;
}
/**
* 构建鉴别器
* @param resultType ResutMap标签的java类型
* @param column 列名
* @param javaType Discriminator标签的java类型
* @param jdbcType Discriminator标签的jdbc类型
* @param typeHandler Discriminator标签的typeHandler
* @param discriminatorMap discriminator 的 case 子元素
* @return
*/
public Discriminator buildDiscriminator(
Class> resultType,
String column,
Class> javaType,
JdbcType jdbcType,
Class extends TypeHandler>> typeHandler,
Map discriminatorMap) {
//构建Discriminator对应的ResultMapping,Discriminator是不支持懒加载的。
ResultMapping resultMapping = buildResultMapping(
resultType,
null,
column,
javaType,
jdbcType,
null,
null,
null,
null,
typeHandler,
new ArrayList<>(),
null,
null,
false);
Map namespaceDiscriminatorMap = new HashMap<>();
for (Map.Entry e : discriminatorMap.entrySet()) {
String resultMap = e.getValue();
resultMap = applyCurrentNamespace(resultMap, true);
namespaceDiscriminatorMap.put(e.getKey(), resultMap);
}
return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
}
/**
* 构建MapperStatement对象,并添加到Mybatis全局配置信息中
* @param id DML标签的ID
* @param sqlSource 动态SQL源
* @param statementType Statement类型
* @param sqlCommandType SQL指令类型
* @param fetchSize 这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动)。
* @param timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。
* @param parameterMap paramterMapId,参数映射信息封装类,Mapper.xml的parameterMap标签
* @param parameterType 参数类型
* @param resultMap resultMapId,结果映射信息封装类,Mapper.xml的resultMap标签 集合
* @param resultType 结果映射类型
* @param resultSetType ResultSetType枚举
* @param flushCache 刷新缓存标记
* @param useCache 使用缓存标记
* @param resultOrdered 参考博客:https://blog.csdn.net/isea533/article/details/51533296?utm_source=blogxgwz9
* @param keyGenerator Key生成器
* @param keyProperty 仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
* @param keyColumn (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。 如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表
* @param databaseId DML标签中配置的databaseId属性
* @param lang 语言驱动,默认是 {@link org.apache.ibatis.scripting.xmltags.XMLLanguageDriver}
* @param resultSets 结果集
* @return MappedStatement对象
*/
public MappedStatement 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) {
//未成功解析CacheRef在这里会抛出异常
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
//检查ID是否简写,简写就应用当前命名空间;
id = applyCurrentNamespace(id, false);
//是否是Select标签
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//构建MappedStatement对象
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
//获取resultMap对应的ResultMaps对象集合
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
//如果不是select指令的话,默认是缓存的
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
//使用缓存,默认如果是select指令是使用缓存的
.useCache(valueOrDefault(useCache, isSelect))
//缓存使用当前的缓存实例
.cache(currentCache);
//获取paramterMap对应ParameterMap对象
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
//构建MapperStatement对象
MappedStatement statement = statementBuilder.build();
//添加到mybatis的全局配置信息中
configuration.addMappedStatement(statement);
return statement;
}
/**
* 当value为null时,返回default
* @param value 值
* @param defaultValue 默认值
* @param 返回类型
* @return 当value为null时,返回default
*/
private T valueOrDefault(T value, T defaultValue) {
return value == null ? defaultValue : value;
}
/**
* 获取Statment的参数映射
* @param parameterMapName 参数映射Id
* @param parameterTypeClass 参数类型
* @param statementId DML标签的ID
* @return ParameterMap对象
*/
private ParameterMap getStatementParameterMap(
String parameterMapName,
Class> parameterTypeClass,
String statementId) {
//检查ID是否简写,简写就应用当前命名空间;
parameterMapName = applyCurrentNamespace(parameterMapName, true);
ParameterMap parameterMap = null;
if (parameterMapName != null) {
try {
//查找出parameterMapName对应的ParameterMap对象
parameterMap = configuration.getParameterMap(parameterMapName);
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("Could not find parameter map " + parameterMapName, e);
}
} else if (parameterTypeClass != null) {
//构建parameterTypeClass的parameterMap对象
List parameterMappings = new ArrayList<>();
parameterMap = new ParameterMap.Builder(
configuration,
statementId + "-Inline",
parameterTypeClass,
parameterMappings).build();
}
return parameterMap;
}
/**
* 获取Statment的结果映射集合
* @param resultMap resultMapId
* @param resultType 结果映射类型
* @param statementId DML标签的ID
* @return ResultMap对象集合
*/
private List getStatementResultMaps(
String resultMap,
Class> resultType,
String statementId) {
//检查ID是否简写,简写就应用当前命名空间;
resultMap = applyCurrentNamespace(resultMap, true);
List resultMaps = new ArrayList<>();
if (resultMap != null) {
//查找出resultMapName对应的resultMap对象,并添加到resultMaps中
String[] resultMapNames = resultMap.split(",");
for (String resultMapName : resultMapNames) {
try {
resultMaps.add(configuration.getResultMap(resultMapName.trim()));
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("Could not find result map '" + resultMapName + "' referenced from '" + statementId + "'", e);
}
}
} else if (resultType != null) {
//构建resultType的ResultMap
ResultMap inlineResultMap = new ResultMap.Builder(
configuration,
statementId + "-Inline",
resultType,
new ArrayList<>(),
null).build();
resultMaps.add(inlineResultMap);
}
return resultMaps;
}
/**
* 对ResutMap标签的result标签封装ResultMapping对象
* @param resultType resultMap标签对应的javaType
* @param property 属性名
* @param column 列名
* @param javaType 属性的java类型
* @param jdbcType 属性的jdbc类型
* @param nestedSelect 嵌套的 select id
* @param nestedResultMap 获取嵌套的 resultMap id
* @param notNullColumn 获取指定的不为空才创建实例的列
* @param columnPrefix 列前缀,columnPrefix用法:https://www.cnblogs.com/circlebreak/p/6647710.html
* @param typeHandler 类型转换器
* @param flags 属性标记
* @param resultSet 集合的多结果集
* @param foreignColumn 指定外键对应的列名
* @param lazy 懒加载
*/
public ResultMapping buildResultMapping(
Class> resultType,
String property,
String column,
Class> javaType,
JdbcType jdbcType,
String nestedSelect,
String nestedResultMap,
String notNullColumn,
String columnPrefix,
Class extends TypeHandler>> typeHandler,
List flags,
String resultSet,
String foreignColumn,
boolean lazy) {
/**
* 获取结果属性的javaType,只有javaType为null的情况下,就会自动获取javaType并返回;
* javaType不为null时,直接返回javaType
*/
Class> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
//构建TypeHandler实例,没有注册到{@link #typeHandlerRegistry},只是返回TypeHandler实例对象
TypeHandler> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
//复合映射
List composites = parseCompositeColumnName(column);
//封装ResutMap标签的属性标签封装
return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
.jdbcType(jdbcType)
.nestedQueryId(applyCurrentNamespace(nestedSelect, true))
.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
.resultSet(resultSet)
.typeHandler(typeHandlerInstance)
.flags(flags == null ? new ArrayList<>() : flags)
.composites(composites)
.notNullColumns(parseMultipleColumnNames(notNullColumn))
.columnPrefix(columnPrefix)
.foreignColumn(foreignColumn)
.lazy(lazy)
.build();
}
/**
* 分割列名,并返回
* @param columnName 列名
* @return eg:'{col1,col2}' => [col1,col2]
*/
private Set parseMultipleColumnNames(String columnName) {
Set columns = new HashSet<>();
if (columnName != null) {
if (columnName.indexOf(',') > -1) {
StringTokenizer parser = new StringTokenizer(columnName, "{}, ", false);
while (parser.hasMoreTokens()) {
String column = parser.nextToken();
columns.add(column);
}
} else {
columns.add(columnName);
}
}
return columns;
}
/**
* 解析复合映射
*
* 参考博客:https://www.cnblogs.com/wei-zw/p/9001906.html
*
* @param columnName 列名
* @return ResutMap标签的属性标签封装集合
*/
private List parseCompositeColumnName(String columnName) {
//ResutMap标签的属性标签封装集合
List composites = new ArrayList<>();
//如果columnName不为null 同时colunmnName中含有"=" 或者含有","号
if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
//分割字符串
StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
while (parser.hasMoreTokens()) {
//获取属性
String property = parser.nextToken();
////获取列
String column = parser.nextToken();
/**
* 构建ResutMap标签的属性标签封装,这里的TypeHandler采用的是UnknownTypeHandler,该TypeHandler
* 在{@link BaseTypeHandler}的抽象方法中根据返回的结果集提供的列去获取对应的TypeHandler时候,
* 在获取不到的情况下,就会使用{@link ObjectTypeHandler}处理
*/
ResultMapping complexResultMapping = new ResultMapping.Builder(
configuration, property, column, configuration.getTypeHandlerRegistry().getUnknownTypeHandler()).build();
composites.add(complexResultMapping);
}
}
return composites;
}
/**
* 分析结果属性的javaType
*
* 在{@code javaType}为null的情况下,通过{@code resultType}的反射信息,
* 从中获取对应{@code property}的setter方法的参数类型,没有获取到的情况下,就会返回Object
* ;否则直接返回{@code javaType}
*
* @param resultType 结果类型
* @param property 结果属性
* @param javaType 参数不为null,方法就直接返回参数,不做任何处理;
* @return 结果属性的javaType
*/
private Class> resolveResultJavaType(Class> resultType, String property, Class> javaType) {
if (javaType == null && property != null) {
try {
//封装resultType的反射信息
MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
//获取对应{@code property}的setter方法的参数类型
javaType = metaResultType.getSetterType(property);
} catch (Exception e) {
//ignore, following null check statement will deal with the situation
}
}
if (javaType == null) {
javaType = Object.class;
}
return javaType;
}
/**
* 解析获取参数或者结果的属性的java类型
*
* javaType不为null的情况下,会原封不动的返回javaType,但是为空的话,会反射resultType的property属性的getter方法的返回类型作为javaType。
*
* 一些特殊情况:
*
* - 如果jdbcType等于{@link JdbcType#CURSOR},javaType为{@link java.sql.ResultSet}
* - 如果reusltType是{@link Map}的实现类,javaType为Object
* - 如果到最后还是无法确定javaType的对应类型,默认返回Object
*
* @param resultType 参数或者结果的类型
* @param property 参数或者结果的属性名
* @param javaType 参数或者结果的属性java类型
* @param jdbcType 参数或者结果的属性jdbc类型
*/
private Class> resolveParameterJavaType(Class> resultType, String property, Class> javaType, JdbcType jdbcType) {
if (javaType == null) {
if (JdbcType.CURSOR.equals(jdbcType)) {
// jdbcType为JdbcType.CURSOR,则javaType为java.sql.ResultSet.class
javaType = java.sql.ResultSet.class;
} else if (Map.class.isAssignableFrom(resultType)) {
// resultType为Map系列,javaType为Object.class
javaType = Object.class;
} else {
// MetaClass是mybatis用于简化反射操作的封装类
MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
//用property属性的getter方法的返回类型作为javaType。
javaType = metaResultType.getGetterType(property);
}
}
if (javaType == null) {
javaType = Object.class;
}
return javaType;
}
/** Backward compatibility signature. */
public ResultMapping buildResultMapping(Class> resultType, String property, String column, Class> javaType,
JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix,
Class extends TypeHandler>> typeHandler, List flags) {
return buildResultMapping(
resultType, property, column, javaType, jdbcType, nestedSelect,
nestedResultMap, notNullColumn, columnPrefix, typeHandler, flags, null, null, configuration.isLazyLoadingEnabled());
}
/**
* @deprecated Use {@link Configuration#getLanguageDriver(Class)}
*/
@Deprecated
public LanguageDriver getLanguageDriver(Class extends LanguageDriver> langClass) {
return configuration.getLanguageDriver(langClass);
}
/** Backward compatibility signature. */
public MappedStatement 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) {
return addMappedStatement(
id, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
parameterMap, parameterType, resultMap, resultType, resultSetType,
flushCache, useCache, resultOrdered, keyGenerator, keyProperty,
keyColumn, databaseId, lang, null);
}
}
XMLStatementBuilder
/**
* Copyright 2009-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.builder.xml;
import java.util.List;
import java.util.Locale;
import org.apache.ibatis.builder.BaseBuilder;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultSetType;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
/**
* @author Clinton Begin
*/
public class XMLStatementBuilder extends BaseBuilder {
/**
* 构建映射器助理
*/
private final MapperBuilderAssistant builderAssistant;
/**
* select|insert|update|delete 标签,下面简称DML标签
*/
private final XNode context;
/**
* 当前项目引用的dataBaseId
*/
private final String requiredDatabaseId;
public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context) {
this(configuration, builderAssistant, context, null);
}
/**
*
* @param configuration mybatis全局配置信息
* @param builderAssistant 构建映射器助理
* @param context select|insert|update|delete 标签
* @param databaseId 当前项目引用的dataBaseId
*/
public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
super(configuration);
this.builderAssistant = builderAssistant;
this.context = context;
this.requiredDatabaseId = databaseId;
}
/**
* 解析DML标签
* 将DML标签中的include标签替换成对应sql标签下的所有子标签
* 将DML标签中的selectKey标签封装成KeyGenerator对象,然后添加到Mybatis全局配置信息信息,然后删除DML标签里的所有selectKey标签
* 将DML标签,封装成MapperStatement对象,然后添加到Mybatis全局配置信息中
*/
public void parseStatementNode() {
//获取DML标签的id属性
String id = context.getStringAttribute("id");
//获取DML标签的databaseId属性
String databaseId = context.getStringAttribute("databaseId");
//如果当前DML标签不对应当前配置的数据库Id
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
//结束方法
return;
}
//获取DML标签名:select|insert|update|delete 标签
String nodeName = context.getNode().getNodeName();
//获取对应的DML标签名的SQLCommentType枚举实例
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
//只有不是select语句,isSelect才会为true
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//获取DML标签的flushCache属性,flushCache表示执行DML语句时是否刷新缓存,默认是只要不是select语句就会刷新缓存
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//获取DML标签的flushCache属性,useCache表示是否对该语句进行二级缓存,默认是对select语句进行缓存。
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
/**
* 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,
* 这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。
* 这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。
*/
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
/**
* 处理include标签。其实就是将include标签替换成对应sql标签下的所有子标签
*
* 根据include标签的refid属性值去找到存在mybatis全局配置里的对应的SQL标签,
* 如果sql标签和include标签不在同一个mapper.xml中,会将sql标签加入到与include标签对应的
* mapper.xml中,然后将include标签替换成sql标签,再将sql标签下的所有子节点(sql标签下面的文本也是子节点的一部分)
* 全部复制插入到sql标签的父标签下,又在sql标签前面的位置。这里的sql父标签其实就是DML标签,
* 也就是这里context。最后将sql标签删除。
*
* 在覆盖过程中,还会对include标签下的property标签传递给sql标签的'${...}'参数,并更改掉
*/
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
/**
* 获取DML标签的parameterType属性。
* parmeterType:将要传入语句的参数的完全限定类名或别名。这个属性是可选的,
* 因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。
*/
String parameterType = context.getStringAttribute("parameterType");
Class> parameterTypeClass = resolveClass(parameterType);
/**
* 获取DML标签的lang属性:
* lang:指定该DML标签使用的语言驱动
* 语言驱动:MyBatis 从 3.2 开始支持可插拔脚本语言,这允许你插入一种脚本语言驱动,
* 并基于这种语言来编写动态 SQL 查询语句
*/
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
/**
* 获取DML标签中的所有对应配置的当前项目引用的databaseId的SelectKey标签,获取没有配置databaseId的SelectKey标签,
* 遍历selectKey标签集合,将selectKey标签封装成KeyGenerator,然后添加到Mybatis全局配置信息中,
* 然后删除DML标签中的所有selectKey标签
*/
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
KeyGenerator keyGenerator;
//拼装selectKey标签的Id,selectKey标签的Id=DML标签ID+!selectKey
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
//检查ID是否简写,简写就应用当前命名空间
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
//如果Mybatis全局配置信息中有keyStatementId对应的KeyGenerator
if (configuration.hasKeyGenerator(keyStatementId)) {
//获取keyGenerator
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
//如果DML标签配置了useGeneratorKeys为true,就使用JDBC3KeyGenertor作为keyGenerator;如果为false,就是用NoKeyGenerator
//默认情况下,配置了MyBatis全局配置信息中的useGenerateKey为true时,只要是SQL是Insert类型的,useGeneratorKeys都会为true.
/**
* Jdbc3KeyGenerator:主要用于数据库的自增主键,比如 MySQL、PostgreSQL;会将执行SQL后从Statemenet中获取主键放到参数对象对应的属性里
* NoKeyGenerator: 什么事情都不干,里面是空实现方法
*/
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//创建DML标签对应的动态SQL
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//获取StatementType,默认情况下是StatementType.PREPARED
/**
* StatementType.STATEMENT:对应于Statement对象,有SQL注入的风险
* StatementType.PREPARED: PreparedStatement,预编译处理,默认
* StatementType.CALLABLE:CallableStatement一般调用存储过程的时候使用
*/
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
//fetchSize:尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)。
Integer fetchSize = context.getIntAttribute("fetchSize");
//timeout:这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
Integer timeout = context.getIntAttribute("timeout");
//获取参数映射ID,对应于parameterMap标签
String parameterMap = context.getStringAttribute("parameterMap");
/**
* 获取结果的类型。MyBatis 通常可以推断出来,但是为了更加精确,写上也不会有什么问题。
* MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。
*/
String resultType = context.getStringAttribute("resultType");
//通过类别名注册器typeAliasRegistry解析出对应的类 实际调用{@link #resolveAlias(String)},返回类实例
Class> resultTypeClass = resolveClass(resultType);
// 获取结果映射Id,对应于resultMap标签
String resultMap = context.getStringAttribute("resultMap");
/**
* ResultSetType.DEFAULT:依赖驱动,默认
* ResultSetType.FORWARD_ONLY:结果集的游标只能向下滚动
* ResultSetType.SCROLL_INSENSITIVE:结果集的游标可以上下移动,当数据库变化时,当前结果集不变
* ResultSetType.SCROLL_SENSITIVE:返回可滚动的结果集,当数据库变化时,当前结果集同步改变
*/
String resultSetType = context.getStringAttribute("resultSetType");
//将resultSetType转换成ResultSetType枚举
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
/**
* (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过
* getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,
* 默认值:未设置(unset)。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
*/
String keyProperty = context.getStringAttribute("keyProperty");
/**
* (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库
* (像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。
* 如果希望使用多个生成的列,也可以设置为逗号分隔的属性名称列表。
*/
String keyColumn = context.getStringAttribute("keyColumn");
/**
* 这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,
* 名称是逗号分隔的。
*/
String resultSets = context.getStringAttribute("resultSets");
// 构建MapperStatement对象,并添加到全局配置信息中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
/**
* 获取DML标签中的所有对应配置的当前项目引用的databaseId的SelectKey标签,获取没有配置databaseId的SelectKey标签,
* 遍历selectKey标签集合,将selectKey标签封装成KeyGenerator,然后添加到Mybatis全局配置信息中。
* 然后删除DML标签中的所有selectKey标签
* @param id DML标签的id属性
* @param parameterTypeClass DML标签的parameterType属性
* @param langDriver 语言驱动
*/
private void processSelectKeyNodes(String id, Class> parameterTypeClass, LanguageDriver langDriver) {
//获取所有在DML标签里的selectKey标签
List selectKeyNodes = context.evalNodes("selectKey");
//如果配置的当前项目引用的databaseId
if (configuration.getDatabaseId() != null) {
/**
* 解析多个selectKey标签,遍历selectKey标签集合,将selectKey标签封装成KeyGenerator,然后
* 添加到Mybatis全局配置信息中
*/
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
}
/**
* 解析多个selectKey标签,遍历selectKey标签集合,将selectKey标签封装成KeyGenerator,然后
* 添加到Mybatis全局配置信息中
*/
//MyBatis认为没有配置dataBaseId的SelectKey标签,默认是对应当前项目引用的databaseId
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
//移除DML标签里的所有SelectKey标签
removeSelectKeyNodes(selectKeyNodes);
}
/**
* 解析多个selectKey标签,遍历selectKey标签集合,将selectKey标签封装成KeyGenerator,然后
* 添加到Mybatis全局配置信息中
* @param parentId DML标签的id属性值
* @param list selectKey标签集合
* @param parameterTypeClass DML标签的parameterType
* @param langDriver 语言驱动
* @param skRequiredDatabaseId 当前项目应用的dataBaseId
*/
private void parseSelectKeyNodes(String parentId, List list, Class> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
for (XNode nodeToHandle : list) {
//id = DML标签的ID属性值+!selectKey
String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
//获取selectKey标签的databaseId属性
String databaseId = nodeToHandle.getStringAttribute("databaseId");
//判断selectKey标签是属于当前项目引用的dataBaseId
if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
//解析单个selectKey标签,将selectKey标签封装成KeyGenerator,然后添加到Mybatis全局配置信息中
parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
}
}
}
/**
* 解析单个selectKey标签,将selectKey标签封装成KeyGenerator,然后添加到Mybatis全局配置信息中
* @param id DML标签的ID属性值+!selectKey
* @param nodeToHandle selectKey标签
* @param parameterTypeClass DML标签的parameterType
* @param langDriver 语言驱动
* @param databaseId selectKey标签的dataBaseId,也是 当前项目应用的dataBaseId
*/
private void parseSelectKeyNode(String id, XNode nodeToHandle, Class> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
//获取selectKey标签的resultType属性
String resultType = nodeToHandle.getStringAttribute("resultType");
Class> resultTypeClass = resolveClass(resultType);
/**
* 获取selectKey标签的statementType属性,默认是StatementType.PREPARED
* StatementType:STATEMENT(对应于Statement对象,有SQL注入的风险);PreparedStatement(预编译处理);
* CallableStatement(一般调用存储过程的时候使用)
*/
StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
// 获取selectKey标签的keyProperty属性。keyProperty:对应于parameterTypeClass的属性
String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
// 获取selectKey标签的keyColumn属性。keyColumn:对应于表中的的列名
String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
//获取selectKey标签的order属性。order有两个值:BEFORE:表示在执行SQL之前执行,AFTER:表示执行SQL之后执行
boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));
//defaults 下面代码为默认配置
//不使用缓存
boolean useCache = false;
/**
*
* 如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,
* 就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。
* 默认值:false。
*
*
* 参考博客:https://blog.csdn.net/isea533/article/details/51533296?utm_source=blogxgwz9
*
*/
boolean resultOrdered = false;
//不会生成任何Key
KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
/**
*
* 这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动)。
*
*
*
* 假设fetchSize为5,查询总记录数为100,每批5次返回给你,最后结果还是总记录数,只是提高点查询的速度而已
*
*
*
* MySQL不支持fetchSize,默认为一次性取出所有数据。所以容易导致OOM,
* 如果是Oracle的话就是默认取出fetchSize条数据。
* 裸露JDBC防止OOM可以调用statement的enableStreamingResults方法,
* MyBatis应该在<select fetchSize="-2147483648">。
*
*/
Integer fetchSize = null;
//超时时间
Integer timeout = null;
//不刷新缓存
boolean flushCache = false;
String parameterMap = null;
String resultMap = null;
ResultSetType resultSetTypeEnum = null;
//动态SQL源
SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
//selectKey的SQL一定要是SELECT类型的
SqlCommandType sqlCommandType = SqlCommandType.SELECT;
//构建MapperStatement对象,并添加到全局配置信息中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);
//检查ID是否简写,简写就应用当前命名空间;
id = builderAssistant.applyCurrentNamespace(id, false);
//因为已经添加到全局配置信息中,所以可以直接获取,keyStatement就是上面的代码构建出来的MappedStatement对象
MappedStatement keyStatement = configuration.getMappedStatement(id, false);
//新建一个SelectKeyGenerator对象,然后添加到Mybatis全局配置信息中
configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}
/**
* 移除在DML标签中 {@code selectKeyNodes} 中的所有selectKey标签
* @param selectKeyNodes selectKey标签集合
*/
private void removeSelectKeyNodes(List selectKeyNodes) {
for (XNode nodeToHandle : selectKeyNodes) {
nodeToHandle.getParent().getNode().removeChild(nodeToHandle.getNode());
}
}
/**
* 判断DML标签是属于当前项目引用的dataBaseId
* @param id DML标签的ID属性
* @param databaseId DML标签的databBaseId属性
* @param requiredDatabaseId 当前项目引用的dataBaseId
* @return
*/
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
//当前项目引用的dataBaseId是否等于当前项目引用的dataBaseId
if (requiredDatabaseId != null) {
return requiredDatabaseId.equals(databaseId);
}
/**
* 到这里requiredDatabaseId就为null,也因为不设置databaseId默认就是对应当前项目引用的dataBaseId,
* 所以就算dataBaseId有设置,当前项目引用的dataBaseId没有设置,
* 就相当于一个有值的字符串判断一个空字符串一样,所以认为是不匹配的。
*/
if (databaseId != null) {
return false;
}
//检查ID是否简写,简写就应用当前命名空间;
id = builderAssistant.applyCurrentNamespace(id, false);
/**
* 没有加入configuration的mappedStatments,统一返回true。因为到这里databaseId就是为null的情况,
* mybati对不设置databaseId默认认为就是对应当前项目引用的dataBaseId,就相当于
* 一个空字符串判断一个空字符串一样,所以认为是匹配的。
*/
if (!this.configuration.hasStatement(id, false)) {
return true;
}
/**
* 下面代码触发条件是:databaseId为null,使得databaseId不等于requiredDatabaseId,id已经存在
* configuration的mappedStatments中。
* 如果我没有推测错误的话,mybatis认为没有设置databaseId的DML标签,是不够明确这个DML标签就是
* 对应于当前项目引用的dataBaseId,所以当出现多个相同id的DML标签时,mybatis会以最后一个作为真正
* 对应于当前项目引用的dataBaseId的DML标签,所以这里如果上一个DML标签没有设置databaseId话,
* 会覆盖掉上一个DML标签。
*/
// skip this statement if there is a previous one with a not null databaseId
MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2
return previous.getDatabaseId() == null;
}
/**
* 获取语言驱动
* @param lang 别名或者包+类名
* @return 当 {@code lang} 为null,会获取默认的语言驱动实例
*/
private LanguageDriver getLanguageDriver(String lang) {
Class extends LanguageDriver> langClass = null;
if (lang != null) {
langClass = resolveClass(lang);
}
return configuration.getLanguageDriver(langClass);
}
}