mybatis源码学习------resultMap和sql片段的解析
resultMap的解析
书接上回,mybatis对于<resultMap></resultMap>
标签解析的方法入口为:
resultMapElements(List list)
根据mybatis-3-mapper.dtd文件中对于resultMap的定义可知,一个mapper节点内可以定义任意多个resultMap节点,所以resultMapElements方法会遍历所有的resultMap进行解析,代码如下:
private void resultMapElements(List<XNode> list) {
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
resultMapElement(XNode)
调用重载方法,三个参数的resultMapElement方法主要用于后续递归调用,所以这里单参数的resultMapElement方法更像是一个“桥接”方法。
private ResultMap resultMapElement(XNode resultMapNode) {
return resultMapElement(resultMapNode, Collections.emptyList(), null);
}
resultMapElement(XNod, List, Class)方法
resultMapElement方法会为<resultMap></resultMap>
中配置的每一个或标签都创建一个ResultMapping对象,并将其保存在一个集合中,最后传递给ResultMapResolver对象进行解析。
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//因为<resultMap/>标签的配置存在嵌套的情况,所以在代码处理嵌套情况的时候一定会使用递归,
//这里获取默认值的逻辑主要是为了处理<resultMap/>嵌套<resultMap/>的情况
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
//解析type属性所配置的类
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null; //resultMappings集合中维护了当前<resultMap/>中所有的映射关系
List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {//处理构造器
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {//处理鉴别器
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {//处理其他情况
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
//获取resultMap节点配置的ID
//getValueBasedIdentifier()方法返回的格式为 类型1[ID1]_类型2[ID2]......
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//获取resultMap节点配置的继承关系
String extend = resultMapNode.getStringAttribute("extends");
//获取是否开启自动映射配置
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//创建ResultMap解析器实例
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
//将具体的解析任务委派给MapperBuilderAssistant的实例
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
解析<constructor/>
processConstructorElement方法对构造函数节点中的ID进行了特殊的标识,便于在后续的解析中区分对id和idArg进行区分,如下图所示,:
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) {
List<XNode> argChildren = resultChild.getChildren();
for (XNode argChild : argChildren) {
List<ResultFlag> flags = new ArrayList<>();
//进行标记
flags.add(ResultFlag.CONSTRUCTOR);
if ("idArg".equals(argChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
}
}
调用buildResultMappingFromContext方法进行解析
buildResultMappingFromContext方法
从功能上说,本方法只负责将用户在xml中配置的信息读取出来,解析的部分则交给MapperBuilderAssistant#buildResultMapping去完成
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
//获取构造方法形参的名字的配置。从 3.4.3 版本开始,通过指定具体的参数名,你可以以任意顺序写入 arg 元素
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");
//获取嵌套查询映射语句的ID
String nestedSelect = context.getStringAttribute("select");
//获取嵌套结果集映射的ID,
String nestedResultMap = context.getStringAttribute("resultMap", () ->
processNestedResultMappings(context, Collections.emptyList(), resultType));
//用户指定非空的列
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"));
//根据用户配置解析javaType对应的类型
Class<?> javaTypeClass = resolveClass(javaType);
//根据用户配置解析typeHandler对应的类型
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
//根据用户配置解析jdbcType对应的类型
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
//委派给BuilderAssistant实例对其进行进一步的解析
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
MapperBuilderAssistant#buildResultMapping
该方法主要对上一步解析出来的配置进行实例化,之后再调用ResultMapping.Builder#build()方法对用户的配置做进一步的解析并创建对应的ResultMapping对象。
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<ResultFlag> flags,
String resultSet,
String foreignColumn,
boolean lazy) {
//对结果类型进行类型推断
Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
//对用户配置的类型拦截器进行实例化
TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
//composites集合主要保存column属性拆分后生成的结果,在嵌套查询的模式下,column的配置会作为参数传递给目标select语句
List<ResultMapping> composites;
if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {
composites = Collections.emptyList();
} else {
composites = parseCompositeColumnName(column);
}
//使用建造者设计模式创建ResultMapping实例
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();
}
ResultMapping.Builder#build()方法
该方法的主要逻辑是对用户的配置进行各种校验,因为无论当前解析的是
public ResultMapping build() {
// lock down collections
resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
//如果用户没有配置对应的TypeHandler,则mybatis会推断具体的类型拦截器
resolveTypeHandler();
//各种校验
validate();
return resultMapping;
}
validate()方法的定义如下,跟我写的代码一样拉胯,就不做分析了:
解析 <discriminator>
官网中对于鉴别器的作用的描述如下:
有时候,一个数据库查询可能会返回多个不同的结果集(但总体上还是有一定的联系的)。 鉴别器(discriminator)元素就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。 鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。
解析discriminator的方法为processDiscriminatorElement方法
mybatis在解析discriminator时会创建一个Map来保存其各个case所对应的resultMap对象,示意图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JqzK9U8W-1604318520352)(/Users/lijiaxing/github/mybatis-3-master/doc/build/解析discriminator的示意图.png)]
//解析鉴别器配置
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) {
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String typeHandler = context.getStringAttribute("typeHandler");
Class<?> javaTypeClass = resolveClass(javaType);
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
Map<String, String> discriminatorMap = new HashMap<>();
for (XNode caseChild : context.getChildren()) {
String value = caseChild.getStringAttribute("value");
//处理嵌套结果映射
String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings, resultType));
discriminatorMap.put(value, resultMap);
}
//调用MapperBuilderAssistant#buildDiscriminator方法来构建Discriminatorshili
return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}
processNestedResultMappings方法
该方法会创建鉴别器map集合中value所指定的resultMap对象
//处理嵌套结果映射
private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) {
if (Arrays.asList("association", "collection", "case").contains(context.getName())
&& context.getStringAttribute("select") == null) {
//校验相关规则
validateCollection(context, enclosingType);
//校验通过后解析对应的resultMap节点
ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
return resultMap.getId();
}
return null;
}
MapperBuilderAssistant#buildDiscriminator
public Discriminator buildDiscriminator(
Class<?> resultType,
String column,
Class<?> javaType,
JdbcType jdbcType,
Class<? extends TypeHandler<?>> typeHandler,
Map<String, String> discriminatorMap) {
//构建ResultMapping对象
ResultMapping resultMapping = buildResultMapping(
resultType,
null,
column,
javaType,
jdbcType,
null,
null,
null,
null,
typeHandler,
new ArrayList<>(),
null,
null,
false);
Map<String, String> namespaceDiscriminatorMap = new HashMap<>();
//遍历map的value,为每一个resultMap的id加上namespace
for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
String resultMap = e.getValue();
resultMap = applyCurrentNamespace(resultMap, true);
namespaceDiscriminatorMap.put(e.getKey(), resultMap);
}
//通过建造者模式创建Discriminator对象
return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
}
解析其他标签
处理其他情况,如<id>
、<result>
、<association>
等标签的代码如下,调用buildResultMappingFromContext方法创建对应的ResultMapping对象,并将其保存在对应的list中即可。
创建并保存ResultMap对象
回顾resultMapElement方法的逻辑,将配置解析为ResultMapping对象并添加到集合中后,接下来要做的就是创建对应的ResultMap对象,并保存在configuration中。
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
//省略
//获取resultMap节点配置的ID
//getValueBasedIdentifier()方法返回的格式为 类型1[ID1]_类型2[ID2]......
//用于处理resultMap嵌套时,内层resultMap没有id的情况
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//获取resultMap节点配置的继承关系
String extend = resultMapNode.getStringAttribute("extends");
//获取是否开启自动映射配置
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//创建ResultMap解析器实例
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
//将具体的解析任务委派给MapperBuilderAssistant的实例
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
由上面的逻辑可知,XmlMapperBuilder在这里创建了一个ResultMapResolver对象,并通过该对象来完成ResultMap对象创建和保存工作。
ResultMapResolver类的定义
public class ResultMapResolver {
private final MapperBuilderAssistant assistant;
private final String id;
private final Class<?> type;
private final String extend;
private final Discriminator discriminator;
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;
}
public ResultMap resolve() {
//委派给MapperBuilderAssistant实例完成创建和保存的工作
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
}
MapperBuilderAssistant#addResultMap
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
//将ID替换为带有命名空间的格式
id = applyCurrentNamespace(id, false);
//将当前resultMap所继承的resultMap的ID替换为带有命名空间的格式
extend = applyCurrentNamespace(extend, true);
//处理继承树
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
ResultMap resultMap = configuration.getResultMap(extend);
//获取父节点中配置的所有ResultMapping对象
List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
//移除重复的ResultMapping配置
extendedResultMappings.removeAll(resultMappings);
//如果子resultMap节点中声明了<constructor><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));
}
//合并父子<resultMap>节点的ResultMapping配置
resultMappings.addAll(extendedResultMappings);
}
//通过建造者设计模式创建ResultMap对象,并将其添加到mybatis的全局配置对象中
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
configuration.addResultMap(resultMap);
return resultMap;
}
Configuration#addResultMap
后面两个方法暂时没看懂是什么意思,先挖个坑,后面再来填
public void addResultMap(ResultMap rm) {
resultMaps.put(rm.getId(), rm);
//TODO
checkLocallyForDiscriminatedNestedResultMaps(rm);
//TODO
checkGloballyForDiscriminatedNestedResultMaps(rm);
}
sql片段的解析
解析sql片段的代码入口为XmlMapperBuilder#sqlElement
sqlElement
private void sqlElement(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
//如果用户配置了databaseId,则会检测当前databaseId与configuration中配置的databaseId是否一致,
//一致则添加到sqlFragments属性中
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
//给id拼接namespace
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
//如果匹配则将其添加到sqlFragments字段中
sqlFragments.put(id, context);
}
}
}
databaseIdMatchesCurrent
//数据库厂商标识是否与当前sql片段的定义相同
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
//如果需要的数据库厂商ID不为空
if (requiredDatabaseId != null) {
return requiredDatabaseId.equals(databaseId);
}
//隐含条件为需要的数据库厂商ID为空
if (databaseId != null) {
return false;
}
//如果sqlFragments集合中不存在对应的sql片段定义
if (!this.sqlFragments.containsKey(id)) {
return true;
}
//如果存在ID对应的sql片段
XNode context = this.sqlFragments.get(id);
//根据上面的if语句可以得出以下,如果程序能走到这里,则requiredDatabaseId一定为null,故直接判断databaseId属性的值是否为null即可
return context.getStringAttribute("databaseId") == null;
}
上一篇: scanf(“%*[\n]%[^\n]“,s)是什么意思?
下一篇: 第10章Python模块和函数
推荐阅读
-
Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?
-
mybatis plus源码解析(一) ---基于springboot配置加载和SqlSessionFactory的构造
-
mybatis源码学习------resultMap和sql片段的解析
-
mybatis源码学习------动态sql的解析(SqlSource)
-
Mybatis 源码学习(二) Mapper 接口和sql的映射
-
Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?
-
MyBatis源码(四)之mapper文件解析和动态Sql解析启动阶段