XMLMapperBuilder
上文我们介绍了通过XMLConfigBuilder
来加载全局配置文件mybatis-config.xml
,读取了各种配置,但里面比较重要的读取mapper,因为我们使用Mybatis框架,更多的关注点实在mapper
映射文件的sql
编写上,接下来我们详细看一下
1.调用链
上文说到在XMLConfigBuilder
类中的parse()
里面调用mapperElement()
读取mapper文件,接下来看一下如何加载mapper
文件
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 第一种情况 通过resource获取mapper.xml
if (resource != null && url == null && mapperClass == null) {
// 错误日志,见单独文章
ErrorContext.instance().resource(resource);
// 读取文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建XMLMapperBuilder
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 转换
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
// 第二种情况 通过url获取mapper.xml
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 第三种情况 通过类路径获取mapper.class
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
上面代码中我们看到可以通过3种方式加载mapper文件,第一种和第二种都是加载xml文件,而第三种是加载我们的dao接口,先介绍如何加载xml文件
加载XML文件,首先读取文件,然后创建了XMLMapperBuilder
,然后调用了parse()
,我们发现这和之前读取全局配置文件用的XMLConfigBuilder
逻辑差不多,其实这两个类都是BaseBuilder
的子类,首先看一下构造方法,就是进行了初始赋值
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
// 构建映射器处理
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
// XML解析器
this.parser = parser;
// SQL碎片
this.sqlFragments = sqlFragments;
// 资源路径
this.resource = resource;
}
解析mapper
文件的parse()
方法
public void parse() {
// 判断该文件是否重复加载
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper标签
configurationElement(parser.evalNode("/mapper"));
// 添加到已加载的集合里面
configuration.addLoadedResource(resource);
// 找出对应当前的mapper的java类型,并添加到全局配置中
bindMapperForNamespace();
}
// 重新解析Mybatis全局配置信息中未能完成解析的ResultMap标签信息
parsePendingResultMaps();
// 重新解析Mybatis全局配置信息中未能完成解析的CacheRef标签信息
parsePendingCacheRefs();
// 重新解析Mybatis全局配置信息中未能完成解析的DML标签信息
parsePendingStatements();
}
我们看到在parse()
方法里面调用了很多方法,其实这些方法大都在同一个类中,下面会依次解析,先看如何解析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");
}
// 设置当前namespace
builderAssistant.setCurrentNamespace(namespace);
// 解析cache-ref 其它命名空间的缓存配置
cacheRefElement(context.evalNode("cache-ref"));
// 解析 该命名空间的缓存配置
cacheElement(context.evalNode("cache"));
// 解析parameterMap标签 但是该标签已经被官网标注废弃
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);
}
}
上面的方法中会把mapper映射的允许使用的标签全部解析,同样从这里我们可以看到mapper
文件允许的标签,这些可以在org.apache.ibatis.builder.xml
的mybatis-3-mapper.dtd
里面看到,下面会介绍一些常用标签的使用
<!ELEMENT mapper (cache-ref | cache | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select* )+>
2.解析cache标签
private void cacheElement(XNode context) throws Exception {
if (context != null) {
// type:指定自定义缓存的全类名(实现Cache接口即可),PERPETUAL是别名,对应PerpetualCache缓存类
// 存储二级缓存的底层结构 可以自定义
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// 缓存回收策略 默认LRU
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);
// 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
// 创建cache实例
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
看一下useNewCache()
方法,该方法通过CacheBuilder()
创建缓存,然后存到全局配置中
// 该方法在 builderAssistant 助理类中
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;
}
3.解析resultMap标签
调用解析resultMap
标签的方法resultMapElements()
,这个需要重点解析一下
// 遍历标签挨个解析
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
// 解析
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
// 第2个参数为空集合
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}
/**
* 解析resultMap标签,构建resultMap实例
* @param resultMapNode resultMap节点
* @param additionalResultMappings 额外的resultMapping
* @return 构建成功的ResultMap实例
*/
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
// 存储操作信息 当发现异常的时候 便于显示
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 获取id 没有设置mybatis就会自动配置一个
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//ResultMap对应的javaType的包名+类名,从这里可以看出,有四个属性都可以指定javaType,优先级为:type=>ofType=>resultType=>javaType
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
String extend = resultMapNode.getStringAttribute("extends");
// 自动映射
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// 结果类型
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
// 映射结果
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
// 子标签
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
// 子标签为constructor 调用指定的构造方法
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
// 子标签为discriminator 判断
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
// 创建一个集合
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
// 这里存储一个枚举,因为解析上面的constructor和这里的id底层解析是用的同一个方法
// 所以需要通过参数来区分 所以这里编辑了一下
flags.add(ResultFlag.ID);
}
// 通过buildResultMappingFromContext创建ResultMapping
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
// 创建resultMap解析器
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
// 调用解析方法
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
// 异常时,将XML Statment构建器添加到Mybatis全局配置信息中,后期再尝试解析
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
在上面的方法中为了解析constructor
,discriminator
和其它标签,又分别调用了另外的方法,现在放在一起,仔细看一下,就是挨个通过遍历,挨个获取属性,然后创建 RequestMapping
// 解析constructor标签
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
List<XNode> argChildren = resultChild.getChildren();
for (XNode argChild : argChildren) {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
// 注意这里: 如果是arg标签 则flags里面只有1个枚举
flags.add(ResultFlag.CONSTRUCTOR);
// 如果是idArg标签 则flags里面有2个枚举
if ("idArg".equals(argChild.getName())) {
flags.add(ResultFlag.ID);
}
// 构造方法标签直接添加到resultMappings集合中
resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
}
}
// 解析discriminator鉴别器
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String typeHandler = context.getStringAttribute("typeHandler");
Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
Map<String, String> discriminatorMap = new HashMap<String, String>();
for (XNode caseChild : context.getChildren()) {
String value = caseChild.getStringAttribute("value");
String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
discriminatorMap.put(value, resultMap);
}
return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}
// 解析其余标签
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
String property;
// 通过传递的参数来区分是解析constructor标签还是其它
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
// 判断嵌套查询
String nestedSelect = context.getStringAttribute("select");
// 判断嵌套的结果映射
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
// 懒加载 从这里可以看出优先读取标签内设定的,没有则读取全局文件中配置的
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
在上面代码块中的第3个方法buildResultMappingFromContext()
,这个方法主要解析除了constructor
和discriminator
外的其它标签,包括:id
,result
,association
,collection
标签,那么例如id
和result
这个很简单,直接封装到RequestMapping
对象里面即可,那么这个1对1,和1对多,如何解析,这个需要看一下processNestedResultMappings()
,
private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception {
// 判断标签名
if ("association".equals(context.getName())
|| "collection".equals(context.getName())
|| "case".equals(context.getName())) {
if (context.getStringAttribute("select") == null) {
// 主要看这里 会继续调用resultMapElement()方法
// 上面解析resultMap标签的时候 用这个方法 这里解析association、collection、case 相当于嵌套解析 类似于递归
// 经过这个处理以后,已经生成一个resultMap,并且把它添加到了configuration里面
ResultMap resultMap = resultMapElement(context, resultMappings);
// 返回结果 下面是几个结果示例
// com.jianan.springtest.dao.CarMapper.mapper_resultMap[list]_association[person]
// com.jianan.springtest.dao.CarMapper.mapper_resultMap[list]_collection[persons]
return resultMap.getId();
}
}
return null;
}
ResultMapping
上面的代码思路,主要是从resultMap
标签获取属性,然后遍历子标签,分别调用方法,把内容封装到ResultMapping
中,我们继续回到resultMapElement()
,方法最后又创建了ResultMapResolver
解析器,进行解析,然后我们先后介绍一下RequestMapping
和 ResultMapResolver
两个类的结构
// 这里简单看一下它的字段
public class ResultMapping {
private Configuration configuration;
// java字段名
private String property;
// jdbc列名
private String column;
private Class<?> javaType;
private JdbcType jdbcType;
// 类型处理器
private TypeHandler<?> typeHandler;
// 嵌套的resultMap 这里对应着其它resultMap的id
private String nestedResultMapId;
// 嵌套的select查询
private String nestedQueryId;
private Set<String> notNullColumns;
private String columnPrefix;
// 处理后的标志,标志共两个:id和constructor
private List<ResultFlag> flags;
private List<ResultMapping> composites;
private String resultSet;
private String foreignColumn;
// 延迟加载
private boolean lazy;
}
ResultMapResolver
public class ResultMapResolver {
// mapper创建助理
private final MapperBuilderAssistant assistant;
// 主键
private final String id;
// 结果类型
private final Class<?> type;
private final String extend;
// discriminator标签
private final Discriminator discriminator;
// resultMap中的子标签集合 里面包含子标签和构造方法标签
private final List<ResultMapping> resultMappings;
private final Boolean autoMapping;
// 构造方法
public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
this.assistant = assistant;
this.id = id;
this.type = type;
this.extend = extend;
this.discriminator = discriminator;
this.resultMappings = resultMappings;
this.autoMapping = autoMapping;
}
// 解析获取ResultMap对象
public ResultMap resolve() {
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
}
上面通过助理类创建ResultMap
,接下来继续看下resolve()
中调用的addResultMap()
// 这个方法看着很长 其实咱们用的部分很少
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
id = applyCurrentNamespace(id, false);
extend = applyCurrentNamespace(extend, true);
// 注意看这里 会判断extend 这个属性我们很少用 所以这里跳过
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
ResultMap resultMap = configuration.getResultMap(extend);
List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
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) {
Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
extendedResultMappingsIter.remove();
}
}
}
resultMappings.addAll(extendedResultMappings);
}
// 这里创建的是ResultMap的内部类Builder,然后通过build()方法,创建ResultMap
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
// 通过这个build()方法来构建
.build();
// 添加到配置文件中
configuration.addResultMap(resultMap);
return resultMap;
}
ResultMap
上面代码经过判断,首先会创建ResultMap
,然后添加到configuration
里面,分别看一下,首先我们看一下ResultMap
类
// 1个ResultMap对应1个resultMap标签,里面的RequestMapping对应子标签
public class ResultMap {
private Configuration configuration;
// <resultMap>的id属性
private String id;
// <resultMap>的type属性
private Class<?> type;
// 除<discriminator>元素外的所有属性映射关系
private List<ResultMapping> resultMappings;
// 所有属性映射中带有ID标志的映射关系,包括<id>元素和<constructor>的<idArg>子元素
private List<ResultMapping> idResultMappings;
// 带有Constructor标志的映射关系
private List<ResultMapping> constructorResultMappings;
// 所有属性映射中不带有Constructor标志的映射关系
private List<ResultMapping> propertyResultMappings;
// 所有属性映射中的column属性的集合
private Set<String> mappedColumns;
// 所有属性映射中的properties属性的集合
private Set<String> mappedProperties;
// 鉴别器
private Discriminator discriminator;
// 是否包含嵌套的结果映射
private boolean hasNestedResultMaps;
// 是否包含嵌套的结果查询
private boolean hasNestedQueries;
// 自动映射
private Boolean autoMapping;
// 私有构造方法
private ResultMap() {
}
// 静态内部类
public static class Builder {
// 日志
private static final Log log = LogFactory.getLog(Builder.class);
// 创建对象
private ResultMap resultMap = new ResultMap();
// 重载构造方法
public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings) {
this(configuration, id, type, resultMappings, null);
}
// 创建Builder的时候,代表创建了一个ResultMap
public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings, Boolean autoMapping) {
resultMap.configuration = configuration;
resultMap.id = id;
resultMap.type = type;
resultMap.resultMappings = resultMappings;
resultMap.autoMapping = autoMapping;
}
// 鉴别器赋值
public Builder discriminator(Discriminator discriminator) {
resultMap.discriminator = discriminator;
return this;
}
// 获取类型
public Class<?> type() {
return resultMap.type;
}
// 构建方法
public ResultMap build() {
if (resultMap.id == null) {
throw new IllegalArgumentException("ResultMaps must have an id");
}
// 初始化各种对象
resultMap.mappedColumns = new HashSet<String>();
resultMap.mappedProperties = new HashSet<String>();
resultMap.idResultMappings = new ArrayList<ResultMapping>();
resultMap.constructorResultMappings = new ArrayList<ResultMapping>();
resultMap.propertyResultMappings = new ArrayList<ResultMapping>();
final List<String> constructorArgNames = new ArrayList<String>();
// 遍历所有的子标签
for (ResultMapping resultMapping : resultMap.resultMappings) {
// 嵌套select子查询
// 这里用 || 就是因为我们需要遍历处理子标签 只要有1个子标签是嵌套查新,这个map就包含嵌套查询
resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
// 嵌套映射
resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);
// 列名 添加到集合
final String column = resultMapping.getColumn();
if (column != null) {
resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
} else if (resultMapping.isCompositeResult()) {
for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
final String compositeColumn = compositeResultMapping.getColumn();
if (compositeColumn != null) {
resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
}
}
}
final String property = resultMapping.getProperty();
// 字段名 添加到集合
if(property != null) {
resultMap.mappedProperties.add(property);
}
// 判断子标签是否为构造方法
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
// 添加到构造方法集合
resultMap.constructorResultMappings.add(resultMapping);
// 设置构造方法参数集合
if (resultMapping.getProperty() != null) {
constructorArgNames.add(resultMapping.getProperty());
}
} else {
// 否则添加到propertyResultMappings
resultMap.propertyResultMappings.add(resultMapping);
}
// 判断子标签是否包含id
if (resultMapping.getFlags().contains(ResultFlag.ID)) {
resultMap.idResultMappings.add(resultMapping);
}
}
// 如果没有id标签 那么全部都添加到idResultMappings
if (resultMap.idResultMappings.isEmpty()) {
resultMap.idResultMappings.addAll(resultMap.resultMappings);
}
if (!constructorArgNames.isEmpty()) {
// 获取构造方法的参数名
final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
if (actualArgNames == null) {
throw new BuilderException("Error in result map '" + resultMap.id
+ "'. Failed to find a constructor in '"
+ resultMap.getType().getName() + "' by arg names " + constructorArgNames
+ ". There might be more info in debug log.");
}
// 排序 让constructorResultMappings和构造方法的形参顺序相同
Collections.sort(resultMap.constructorResultMappings, new Comparator<ResultMapping>() {
@Override
public int compare(ResultMapping o1, ResultMapping o2) {
int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
return paramIdx1 - paramIdx2;
}
});
}
// 设置只读
// lock down collections
resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
return resultMap;
}
// 通过constructor标签设置的参数来匹配构造方法
private List<String> argNamesOfMatchingConstructor(List<String> constructorArgNames) {
// 返回类型的构造方法
Constructor<?>[] constructors = resultMap.type.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
// 参数类型
Class<?>[] paramTypes = constructor.getParameterTypes();
if (constructorArgNames.size() == paramTypes.length) {
// 参数名
List<String> paramNames = getArgNames(constructor);
if (constructorArgNames.containsAll(paramNames)
&& argTypesMatch(constructorArgNames, paramTypes, paramNames)) {
return paramNames;
}
}
}
return null;
}
private boolean argTypesMatch(final List<String> constructorArgNames,
Class<?>[] paramTypes, List<String> paramNames) {
for (int i = 0; i < constructorArgNames.size(); i++) {
Class<?> actualType = paramTypes[paramNames.indexOf(constructorArgNames.get(i))];
Class<?> specifiedType = resultMap.constructorResultMappings.get(i).getJavaType();
if (!actualType.equals(specifiedType)) {
if (log.isDebugEnabled()) {
log.debug("While building result map '" + resultMap.id
+ "', found a constructor with arg names " + constructorArgNames
+ ", but the type of '" + constructorArgNames.get(i)
+ "' did not match. Specified: [" + specifiedType.getName() + "] Declared: ["
+ actualType.getName() + "]");
}
return false;
}
}
return true;
}
// 获取构造方法的形参名
private List<String> getArgNames(Constructor<?> constructor) {
if (resultMap.configuration.isUseActualParamName() && Jdk.parameterExists) {
return ParamNameUtil.getParamNames(constructor);
} else {
List<String> paramNames = new ArrayList<String>();
final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
int paramCount = paramAnnotations.length;
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
name = ((Param) annotation).value();
break;
}
}
paramNames.add(name != null ? name : "arg" + paramIndex);
}
return paramNames;
}
}
}
// 省略一些setter/getter....
}
上面看完了创建的代码,接下来会把resultMap
添加到configuration
中,接下来看一下,我这里截取一下重要的
public class Configuration {
// 创建的同样为内部类StrictMap
// key为id value为值
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
// 添加到配置
public void addResultMap(ResultMap rm) {
resultMaps.put(rm.getId(), rm);
checkLocallyForDiscriminatedNestedResultMaps(rm);
checkGloballyForDiscriminatedNestedResultMaps(rm);
}
}
总结:解析resultMap
标签,1个标签对应1个RequestMap
对象,1个RequestMapping
对象,对应1个子标签,如果是1对1或1对多,那么也会创建RequestMap
,这些RequestMap
会以同等级存储到configuration
里面,也就是configuration
里面的resultMaps
,存储不光包括resultMap
标签,连嵌套查询和嵌套映射也会创建成resultMaps
存进去,但这些ResultMap
之间是存在相互引用关系的,那么怎么判断,就是通过上面介绍的ResultMap
中的字段 hasNestedResultMaps
是否包含嵌套映射,hasNestedQueries
是否包含嵌套查询 以及 ResultMapping
中的字段 nestedResultMapId
嵌套映射对应的id nestedQueryId
嵌套查询对应的id。
这次整个resultMap解析就完成了 还是非常麻烦的 ,需要自己Debug挨个查看 ,下面提供Debug结果示例:
4.解析sql标签
解析sql标签的方法sqlElement()
// 解析结果
private final Map<String, XNode> sqlFragments;
// 遍历调用子方法解析
private void sqlElement(List<XNode> list) throws Exception {
// 第一次用于处理 databaseId 与全局Configuration实例的 databaseId 一致的节点,如果全局没有配置 则直接跳过
// 也就是查找和全局配置的databaseId相匹配的sql
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
// 第二次处理节点的 databaseId 为空的情况
// 也就是查找没有配置databaseId的情况
sqlElement(list, null);
}
// 匹配当前配置的数据库Id
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
for (XNode context : list) {
// 注意这里从sql标签获取databaseId属性肯定为null
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
// 检查ID是否简写,简写就应用当前命名空间
// 需要的格式为 包名+id名 例如:com.jianan.mapper.UserMapper.test
// 这里这样做的目的是两个mapper映射文件的sql语句的id允许相同,所以直接通过全部名称来区分
id = builderAssistant.applyCurrentNamespace(id, false);
// 注意看这里和上面会不一样了 因为我们的sql标签没有databaseId属性
// 所以这个判断就主要根据requiredDatabaseId, 如果存在则肯定false 不存在则返回true
// 然后结合上面方法调用了两次
// 当一次调用的时候 requiredDatabaseId存在 则调用 那么下面肯定返回false,也不会put()
// 当二次调用的时候,方法会直接返回true 会直接添加
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
// 这里的存储方法在Configuration类里面被重写了
sqlFragments.put(id, context);
}
}
}
在上面的代码中会通过databaseIdMatchesCurrent()
来判断是否把当前的sql添加到结果集
这个代码整合上面的思路就是:
- 如果全局配置了
databaseId
,那么会找到和其匹配的sql 和 没有设置databaseId
的sql,也就是找到匹配的sql和通用的sql,如果同时找到带有databaseId
和不带databaseId
的相同语句,则后者会被舍弃 (但是我们的sql标签并没有databaseId
属性,那么这么判断有何用?我猜想可能为了以后扩展吧) - 如果全局没有配置
databaseId
,那么就会直接找到没有设置databaseId
的sql,也就是直接找到通用的sql
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
// 全局配置了databaseId
if (requiredDatabaseId != null) {
// 找到和全局databaseId相等的sql
if (!requiredDatabaseId.equals(databaseId)) {
return false;
}
} else {// 全局没有配置databaseId
if (databaseId != null) {
// 全局没有配置 而这个sql配置了 直接跳过
return false;
}
// 这个sql已经被添加了
// 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);
// 这里的意思是两个sql的id相同,一个带databaseId,另一个不带,则会忽略不带的
if (context.getStringAttribute("databaseId") != null) {
return false;
}
}
}
return true;
}
我们的sql
标签是不允许id
重复的,那么在哪里进行判断的?它是通过databaseIdMatchesCurrent()
下面一行代码的put()
方法进行的,那么我们记得我们的map
接口存储时,如果key
相同则会覆盖,这里怎么实现的?
我们首先看一下sqlFragments
是如何来的,我们回到之前XMLConfigBuilder
类的mapperElement()
方法
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
// 这里在构建XMLMapperBuilder对象的时候,会从configuration获取到sqlFragments 然后传入到构造方法
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
接下来就去看一下Configuration
类,我截取部分重要的内容
public class Configuration {
// 发现在加载 Configuration 类的时候就直接初始化了sqlFragments
// 这里创建的是StrictMap
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
// StrictMap为继承Map的内部类
protected static class StrictMap<V> extends HashMap<String, V> {
// 构造方法
public StrictMap(String name) {
super();
this.name = name;
}
public V put(String key, V value) {
// 如果该id已经存在,则抛出异常
if (containsKey(key)) {
throw new IllegalArgumentException(name + " already contains value for " + key);
}
// 报名 例如:com.jianan.mapper.UserMapper.test
if (key.contains(".")) {
// 获取类名 例如: test
final String shortKey = getShortName(key);
if (super.get(shortKey) == null) {
// 第一次添加
super.put(shortKey, value);
} else {
super.put(shortKey, (V) new Ambiguity(shortKey));
}
}
// 第二次添加
return super.put(key, value);
}
}
}
从上面的put()
中我们看到,同一个sql
标签会往集合里面添加2次,2次的value都是这个标签,第1次的key
为全限定名,例如: com.jianan.mapper.UserMapper.test,第2次的key
为简写名,例如:test
5.解析DML标签
解析基础的标签buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
private void buildStatementFromContext(List<XNode> list) {
// 这个大体思路和上面一样
// 第一次用于处理 databaseId 与全局Configuration实例的 databaseId 一致的节点
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
// 第二次匹配通用的sql
buildStatementFromContext(list, null);
}
// 创建sql
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// 创建XMLStatementBuilder对象
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 转换sql节点
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
// 如果发生异常,则添加到不明确的sql集合里面
configuration.addIncompleteStatement(statementParser);
}
}
}
XMLStatementBuilder
上面的代码就是遍历节点,然后依次借助XMLStatementBuilder
类通过parseStatementNode()
节点转换
/**
* 解析DML标签
* <p>将DML标签中的include标签替换成对应sql标签下的所有子标签</p>
* <p>将DML标签中的selectKey标签封装成KeyGenerator对象,然后添加到Mybatis全局配置信息信息,然后删除DML标签里的所有selectKey标签</p>
* <p>将DML标签,封装成MapperStatement对象,然后添加到Mybatis全局配置信息中</p>
*/
public void parseStatementNode() {
// 下面就是依次从标签获取属性
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
// 判断该sql的databaseId是否匹配 和 该sql是否为重复添加
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");
// 这里需要通过TypeAliasRegistry类的resolveClass来解析 因为可能是别名
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
// lang:指定该DML标签使用的语言驱动
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// 结果类型 解析
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
// 默认PreparedStatement
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
// 通过sql标签来区分是 增删改查
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 是否刷新缓存 非select元素设置为true 都会刷新缓存
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
// 是否缓存结果 对 select 元素为 true 代表默认缓存
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
// 将include标签转换成对应的sql片段 并将其中的${xxx}占位符替换为真实的参数
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
// 转换selectKey 之后删除
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
// 英文已经标注了 转换sql
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
// 用于获取生成主键
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
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 = configuration.getKeyGenerator(keyStatementId);
} else {
// 判断
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 构建MapperStatement对象,并添加到全局配置信息中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
上面用到了databaseIdMatchesCurrent()
来判断sql的databaseId
,该
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
// 全局配置了databaseId 则找到匹配的sql
if (requiredDatabaseId != null) {
if (!requiredDatabaseId.equals(databaseId)) {
return false;
}
} else {
// 只在sql里面配置的 不管
if (databaseId != null) {
return false;
}
// skip this statement if there is a previous one with a not null databaseId
// 检查ID是否简写,简写就应用当前命名空间
// 需要的格式为 包名+id名 例如:com.jianan.mapper.UserMapper.test
// 这里这样做的目的是两个mapper映射文件的sql语句的id允许相同,所以直接通过全部名称来区分
id = builderAssistant.applyCurrentNamespace(id, false);
// 判断是否已经包含
if (this.configuration.hasStatement(id, false)) {
MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2
if (previous.getDatabaseId() != null) {
return false;
}
}
}
return true;
}
parseStatementNode()
中通过includeParser.applyIncludes(context.getNode());
来转换include
标签,将其替换成sql片段,并将其中的${xxx}占位符替换为真实的参数
public void applyIncludes(Node source) {
Properties variablesContext = new Properties();
Properties configurationVariables = configuration.getVariables();
if (configurationVariables != null) {
variablesContext.putAll(configurationVariables);
}
applyIncludes(source, variablesContext, false);
}
/**
* Recursively apply includes through all SQL fragments.
* @param source Include node in DOM tree
* @param variablesContext Current context for static variables with values
*/
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
if (source.getNodeName().equals("include")) {
Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
Properties toIncludeContext = getVariablesContext(source, variablesContext);
applyIncludes(toInclude, toIncludeContext, true);
if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
toInclude = source.getOwnerDocument().importNode(toInclude, true);
}
source.getParentNode().replaceChild(toInclude, source);
while (toInclude.hasChildNodes()) {
toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
}
toInclude.getParentNode().removeChild(toInclude);
} else if (source.getNodeType() == Node.ELEMENT_NODE) {
if (included && !variablesContext.isEmpty()) {
// replace variables in attribute values
NamedNodeMap attributes = source.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node attr = attributes.item(i);
attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
}
}
NodeList children = source.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
applyIncludes(children.item(i), variablesContext, included);
}
} else if (included && source.getNodeType() == Node.TEXT_NODE
&& !variablesContext.isEmpty()) {
// replace variables in text node
source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
}
}
parseStatementNode()
我们在方法的最后又是通过builderAssistant
助理类创建MapperStatement
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) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
// 类名转换
id = applyCurrentNamespace(id, false);
// sql语句类型
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 创建对象 该对象为MappedStatement的静态内部类Build
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(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
// parameterMap 以及被标记废弃 这里不做解释
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 构建MappedStatement
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
// 上面用到的 获取ResultMap
private List<ResultMap> getStatementResultMaps(
String resultMap,
Class<?> resultType,
String statementId) {
// 名称转换
resultMap = applyCurrentNamespace(resultMap, true);
// 结果集合
List<ResultMap> resultMaps = new ArrayList<ResultMap>();
if (resultMap != null) {
// 可能多个
String[] resultMapNames = resultMap.split(",");
for (String resultMapName : resultMapNames) {
try {
// 从configuration中取出放到结果集里面
resultMaps.add(configuration.getResultMap(resultMapName.trim()));
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("Could not find result map " + resultMapName, e);
}
}
} else if (resultType != null) {
ResultMap inlineResultMap = new ResultMap.Builder(
configuration,
statementId + "-Inline",
resultType,
new ArrayList<ResultMapping>(),
null).build();
resultMaps.add(inlineResultMap);
}
return resultMaps;
}
MappedStatement
上面会通过MappedStatement
的静态内部类Build
来创建,那么首先看一下MappenStatement
的结构
public final class MappedStatement {
// mapper配置文件名 例如: UserMapper.xml
private String resource;
private Configuration configuration;
// 标签的id
private String id;
// 缓存的对象最多个数 每次对象返回的个数
private Integer fetchSize;
// 超时时间
private Integer timeout;
// 参数类型
private StatementType statementType;
// 结果类型
private ResultSetType resultSetType;
// sql语句
private SqlSource sqlSource;
// 缓存
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
// 刷新缓存
private boolean flushCacheRequired;
// 是否使用缓存 默认true
private boolean useCache;
// 结果排序
private boolean resultOrdered;
//sql语句的类型,如select、update、delete、insert
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
MappedStatement() {
// constructor disabled
}
// 后面省略.....
}
然后看一下其静态内部类
public static class Builder {
// 创建对象MappedStatement
private MappedStatement mappedStatement = new MappedStatement();
// 构造方法
public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
mappedStatement.configuration = configuration;
mappedStatement.id = id;
mappedStatement.sqlSource = sqlSource;
mappedStatement.statementType = StatementType.PREPARED;
mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build();
mappedStatement.resultMaps = new ArrayList<ResultMap>();
mappedStatement.sqlCommandType = sqlCommandType;
mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
String logId = id;
if (configuration.getLogPrefix() != null) {
logId = configuration.getLogPrefix() + id;
}
mappedStatement.statementLog = LogFactory.getLog(logId);
mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
}
// 建造方法
public MappedStatement build() {
// assert关键字就是断言 如果判断条件不成立 则结束执行 避免太多的if/else判断
assert mappedStatement.configuration != null;
assert mappedStatement.id != null;
assert mappedStatement.sqlSource != null;
assert mappedStatement.lang != null;
// 设置只读
mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
return mappedStatement;
}
}
6.通过namespace解析mapper接口
接下来继续往下面介绍parse()
中调用的bindMapperForNamespace()
,该方法的功能就是判断当前映射文件中namespace
对应的接口是否解析
我们在 mybatis-config.xml 中的mapper标签中可以配置扫描类或xml文件,如果只配置了xml文件,那么会通过namespace来获取接口,同时把接口加载进去
// 找出对应当前Mapper.xml的java类型,也就是我们的dao接口,并添加到Mybatis全局配置信息中
private void bindMapperForNamespace() {
// 获取当前的命名空间
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 创建类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
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
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
7.解析异常resultMap
在直接我们在configurationElement()
中解析了resultMap
标签,但是有发生异常的会被保留,这里再次进行解析,相当于在给你一次机会
//重新解析Mybatis全局配置信息中未能完成的ResultMap标签信息
private void parsePendingResultMaps() {
// 获取Mybatis全局配置信息中未能完成的ResultMap标签信息
// 也就是初步解析的时候发生异常的
Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
synchronized (incompleteResultMaps) {
Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
while (iter.hasNext()) {
try {
// 遍历解析
iter.next().resolve();
// 解析成功 则从集合中移除
iter.remove();
} catch (IncompleteElementException e) {
// ResultMap is still missing a resource...
// 抛出异常时,不做任何操作,让未能完成的ResultMap标签信息继续在Mybatis全局配置信息中等待下次解析
}
}
}
}
3.解析异常DML标签
在直接我们在configurationElement()
中解析了DML标签,但是有发生异常的会被保留,这里再次进行解析
private void parsePendingStatements() {
// 获取异常的sql
Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
synchronized (incompleteStatements) {
Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
while (iter.hasNext()) {
try {
iter.next().parseStatementNode();
iter.remove();
} catch (IncompleteElementException e) {
// Statement is still missing a resource...
}
}
}
}