前面分享到第二个容器typeHandlerRegistry时中间穿插了hashmap的解析.现在回来继续mybatis系列.这一节分享mybatis结果映射的最重要的一个结构ResultMap结构.
前一节分享到typeHandler是用来映射某一个mysql列与java属性映射的处理器.我们的查询语句往往都会查询出很多列,需要把很多列映射到不同的java对象中去.记录mysql列与java属性映射的数据结构被mybatis定义为ResultMap.
mybatis解析ResultMap结构流程图如下:
具体构造器标签、鉴别器标签、常规标签是处理方法调用是怎样的呢?见下面的流程图:
好的,流程图就介绍到这里.接下来我们通过源码来分析下ResultMap的解析过程,以及解析后在内存中的存储结构.
1.源码解读
1.1.ResultMap总入口
resultMapElement该方法主要作用有:
- 从ResultMap的根节点(包含嵌套的根节点)出发,收集所有子节点生成的ResultMapping结构.
- 并构建ResultMap自身的属性,比如autoMapping是否开启属性字段映射、嵌套ResultMap的id生成等.
- 将ResultMap结构存放到Configuration的resultMaps中.
// XMLMapperBuilder
private void configurationElement(XNode context) {
// 省略...
// 解析mappper.xml中的所有resultMap标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 省略...
}
private void resultMapElements(List<XNode> list) {
// 省略...
resultMapElement(resultMapNode);
// 省略...
}
private ResultMap resultMapElement(XNode resultMapNode) {
return resultMapElement(resultMapNode, Collections.emptyList(), null);
}
private ResultMap resultMapElement(XNode resultMapNode,
List<ResultMapping> additionalResultMappings,
Class<?> enclosingType) {
ErrorContext.instance().activity("processing "
+ resultMapNode.getValueBasedIdentifier());
// type是resultMap标签的
// ofType是collection标签的
// resultType是select,insert,update,delete标签的
// javaType是id,result,association,collection标签的
// 其他的标签只有type属性,只有collection标签既有ofType又有javaType,
// 而且ofType又在前面,所以
// collection标签如果配置了ofType,那么他的javaType就不生效了.
// 感觉collection标签的javaType和ofType
// 属性效果一样,不要同时写会被覆盖
// 1.获取该标签对应的javaType(对应的java类型)
// 从这里能看出,如果一个标签允许配置多个type,那么优先级为
// 【type】>【ofType】>【resultType】>【javaType】
String type = resultMapNode
.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 这里是根据type获取Class.不太清楚的同学可以参照第一篇文章typeAliasRegistry
Class<?> typeClass = resolveClass(type);
// 2.如果该标签没有javaType,那么看看该标签的父标签是否有type
// (1) 如果是association标签,且该节点上没有resultMap,
// 那么这个Class就是对应property的类型
// (2) 如果是case标签,且该节点上没有resultMap,那么这个Class就是父节点的type类型
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
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,如果写了id属性,那么这个就是自己写的id;
// 如果是嵌套resultMap是没有id属性的.
// 嵌套resultMap的id由resultMapNode.getValueBasedIdentifier()
// 方法推断生成
String id = resultMapNode.getStringAttribute(
"id",resultMapNode.getValueBasedIdentifier());
// 可以继承其他的ResultMap
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
ResultMapResolver resultMapResolver = new ResultMapResolver(
builderAssistant, id, typeClass, extend,
discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
public ResultMap resolve() {
return assistant.addResultMap(this.id, this.type, this.extend,
this.discriminator, this.resultMappings, this.autoMapping);
}
// 3.存放到Configuration的resultMaps中
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
// ResultMap中的id属性值,会判断该id值是否添加了namespace,如果添加了则不加了
// ,如果没添加,需要补全namespace
id = applyCurrentNamespace(id, false);
// 这个是获取继承的ResultMap的id,没继承的话则为null
extend = applyCurrentNamespace(extend, true);
// 这里是ResultMap中有extends属性的,如果ResultMap中没有extends属性不会进入.
if (extend != null) {
// 这里如果有嵌套的ResultMap,那么这个ResultMap应该在解析该
// ResutMap的时候就已经把父RM给解析出来了,而且放到configuration中了
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<>(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) {
extendedResultMappings.removeIf(resultMapping ->
resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
}
resultMappings.addAll(extendedResultMappings);
}
// 构建鉴别器
ResultMap resultMap = new ResultMap
.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
// 存入configuration
configuration.addResultMap(resultMap);
return resultMap;
}
// 嵌套resultMap结构的id生成
public String getValueBasedIdentifier() {
StringBuilder builder = new StringBuilder();
XNode current = this;
// 这里是循环寻找parent,比如<collection>标签是<resultMap>标签的子,
// 先组装ResultMap名称,然后在拼上父的名称
// <resultMap>标签是<mapper>标签的子,也需要拼上父的名称
while (current != null) {
// 当前节点的不需要插入下划线"_".
if (current != this) {
builder.insert(0, "_");
}
// 有id取id,<resultMap>标签
// 没有id取value,<case>标签
// 没有id、value的取property标签,<collection>、<association>标签
String value = current.getStringAttribute("id",
current.getStringAttribute("value",
urrent.getStringAttribute("property", (String) null)));
if (value != null) {
value = value.replace('.', '_');
builder.insert(0, "]");
builder.insert(0,value);
builder.insert(0, "[");
}
builder.insert(0, current.getName());
current = current.getParent();
}
return builder.toString();
}
接下来我们看看构造器和鉴别器是如何处理自己特有的属性呢吧.
1.2.构造器与鉴别器解析
processConstructorElement:该方法的主要作用为解析构造函数标签.存入集合中.供后续判断使用.
// XMLMapperBuilder
private void processConstructorElement(XNode resultChild,
Class<?> resultType,
List<ResultMapping> resultMappings) {
// 获取所有子节点
List<XNode> argChildren = resultChild.getChildren();
// 遍历constructor标签下的所有子标签,构造器可能有多个参数.对应子标签有idArg,arg
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));
}
}
processDiscriminatorElement 该方法的主要作用有:
- 解析鉴别器所属的列字段,还记得前一节讲的五个重要元素(property、column、javaType、jdbcType、typeHandler)
- 构建mysql列返回的不同值选到不同ResultMap的关系映射.
- 构建Discriminator,并将其作为父ResultMap结构的成员变量.
// XMLMapperBuilder
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);
// typeHandler的推断可以我看前面的文章TypeHandlerRegistry
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
Map<String, String> discriminatorMap = new HashMap<>();
for (XNode caseChild : context.getChildren()) {
// 这里面是<case>标签,value就是<case value="1">的value
String value = caseChild.getStringAttribute("value");
// resultMap就是<case>标签里面的标签,包括<association>、<collection>
String resultMap = caseChild.getStringAttribute("resultMap",
processNestedResultMappings(
caseChild, resultMappings, resultType));
discriminatorMap.put(value, resultMap);
}
return builderAssistant.buildDiscriminator(
resultType, column, javaTypeClass,
jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}
1.3.普通字段
buildResultMappingFromContext
// XMLMapperBuilder
private ResultMapping buildResultMappingFromContext(XNode context,
Class<?> resultType, List<ResultFlag> flags) {
String property;
// 如果是从构造方法的子节点进来的
// <constructor> <idArg column="blog_id" javaType="int"/> </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");
// 是否引用外部的resultMap,如果标签树何止了resultMap,
// 那么嵌套里面的resultMap映射无效
String nestedResultMap = context.getStringAttribute("resultMap", () ->
processNestedResultMappings(
context, Collections.emptyList(), resultType));
// association
String notNullColumn = context.getStringAttribute("notNullColumn");
// association
String columnPrefix = context.getStringAttribute("columnPrefix");
// 列和字段对应关系
String typeHandler = context.getStringAttribute("typeHandler");
// association 多结果集,只有在返回两个结果集的时候才有用,
// 一般用于存储过程返回两个结果集的场景,这里略过
String resultSet = context.getStringAttribute("resultSet");
// association 多结果集(略)
String foreignColumn = context.getStringAttribute("foreignColumn");
// association 是否是懒加载
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType",
configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
// 创建对应的结果类型,好寻找对应的typeHandler做相应处理,
// 比如id的javaType为int,那么这里返回的是Integer的Class对象
// 这里如果没定义javaType,那么这里返回的是null
Class<?> javaTypeClass = resolveClass(javaType);
// 这里如果没定义typeHandler,那么这里返回的是null
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
// 这里如果没定义jdbcType,那么这里返回的也是null
// typeHandler的推断可以我看前面的文章TypeHandlerRegistry
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
return builderAssistant.buildResultMapping(
resultType, property, column, javaTypeClass, jdbcTypeEnum,
nestedSelect, nestedResultMap, notNullColumn, columnPrefix,
typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
buildResultMapping
// MapperBuilderAssistant
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) {
// 这里resultType为ResultMap的javaType,
// property为ResultMap下的子标签对应的java字段名,
// 这里是解析property的java类型
// 1.推断该映射对应的javaType.根据该resultType类型通过property找到set方法,
// 根据set方法的第一个字段的类型作为javaType.
Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
// 这里是解析property字段对应的typeHandler,这里如果不指定java字段对应的typeHandler,
// 那么这里返回null,后面用java反射获取.
// 2.解析typeHandler
// typeHandler的推断可以我看前面的文章TypeHandlerRegistry
// 这里如果有写了typeHandler,那么这里会创建我们写的typeHandler对象,
// 如果没有写那么这里TypeHandler返回值为null
TypeHandler<?> typeHandlerInstance
= resolveTypeHandler(javaTypeClass, typeHandler);
List<ResultMapping> composites;
// 如果没有嵌套的select,且foreignColumn【这个是多数据集】为空
if ((nestedSelect == null || nestedSelect.isEmpty())
&& (foreignColumn == null || foreignColumn.isEmpty())) {
composites = Collections.emptyList();
} else {
composites = parseCompositeColumnName(column);
}
return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
.jdbcType(jdbcType)
.nestedQueryId(
applyCurrentNamespace(nestedSelect, true)) // select 绑上当前的命名空间
.nestedResultMapId(
applyCurrentNamespace(nestedResultMap, true)) // select 绑上当前的命名空间
.resultSet(resultSet)
.typeHandler(typeHandlerInstance)
.flags(flags == null ? new ArrayList<>() : flags)
.composites(composites)
.notNullColumns(parseMultipleColumnNames(notNullColumn))
.columnPrefix(columnPrefix)
.foreignColumn(foreignColumn) // 多数据源(略)
.lazy(lazy)
.build();
}
1.4.嵌套ResultMap
processNestedResultMappings 该方法的主要作用有
- 判断该标签是否是嵌套的ResultMap
- 递归调用resultMapElement方法,解析嵌套ResultMap结构并生成ResultMap的id
- 将生成的嵌套的ResultMap的id返回给父ResultMap,作为一个ResultMap的ResultMapping的一个属性.
// XMLMapperBuilder
private String processNestedResultMappings(XNode context,
List<ResultMapping> resultMappings,
Class<?> enclosingType) {
// 如果是association,collection,case标签,且不包含select属性
if (Arrays.asList("association", "collection", "case").contains(context.getName())
&& context.getStringAttribute("select") == null) {
validateCollection(context, enclosingType);
// 递归调用1.1
ResultMap resultMap = resultMapElement(
context, resultMappings, enclosingType);
return resultMap.getId();
}
return null;
}
从上面代码上能看得出成为嵌套ResultMap的规则为:association,collection,case标签,且不包含select属性.
2.举例
上面源码已经分析完毕,下面举例来分享下ResultMap的数据结构.
2.1.简单映射
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL COMMENT '角色名称',
`sort` int(11) DEFAULT NULL COMMENT '排序',
PRIMARY KEY (`id`)
);
public class Role {
private Integer id;
private String name;
private Integer sort;
// getter,setter省略
public Role(Integer id) {
this.id = id;
}
}
<mapper namespace="test.vlog.mapper.RoleMapper">
<resultMap id="roleEntity" type="test.vlog.domian.Role">
<constructor>
<idArg column="id" javaType="int"></idArg>
</constructor>
<result column="name" property="name"/>
<result column="sort" property="sort"/>
</resultMap>
<select id="getRoleList" resultMap="roleEntity">
select id,name,sort from role;
</select>
</mapper>
roleEntity的ResultMap结构经过第一节的4个方法数据解析过程见下图:
经过上述解析过程,最终形成ResultMapping集合结果如下图:
ResultMap的id生成规则如下图:
根据ResultMapping结合生成ResultMap结构规则:
- (boolean)nestedQueryId(嵌套查询)生成规则:循环遍历每一个ResultMapping,只要有一个ResultMapping的nestedQueryId不为null的,就认为有嵌套查询.
- (boolean)hasNestedResultMaps(嵌套RM)生成规则:循环遍历每一个ResultMapping,只要有一个ResultMapping有nestedResultMapId,就认为有嵌套的ResultMap.
- (Set<String>)mappedColumns(列参数)生成规则:循环遍历每一个ResultMapping,列名(column)大写存入.
- (Set<String>)mappedProperties(属性参数)生成规则:循环遍历每一个ResultMapping,属性名(property)写入.
- (List<ResultMapping>)constructorResultMappings(构造函数参数)生成规则:如果写了构造函数的idArg的name,但是编译的时候,方法的参数名没有编译进来,就会导致方法的参数名为args0、args1等.会导致与constructor标签中name不一致,从而报错.所以建议不要写constructor的name属性.
- (List<ResultMapping>)propertyResultMapping生成规则:非constructorResultMappings写入.
- (List<ResultMapping>)idResultMappings生成规则:id标签的ResultMapping写入.
最终形成ResultMap结构如下图:
最终在Configuration的resultMaps中结构如下图:
2.2.嵌套映射
嵌套映射多指一对多或多对多的映射.下面是角色到用户的一个表结构.角色与用户的对应关系为一对多.
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`role_id` int(11) DEFAULT NULL COMMENT '角色id',
`name` varchar(40) DEFAULT NULL COMMENT '用户名称',
`alisa` varchar(40) DEFAULT NULL COMMENT '别名',
PRIMARY KEY (`id`)
);
// 角色表见2.1.
public class User {
private Integer id;
private Integer roleId;
private String name;
private String alisa;
// getter、setter省略
}
public class Role {
private Integer id;
private String name;
private Integer sort;
private List<User> users;
// getter、setter省略
}
<mapper namespace="test.vlog.mapper.RoleMapper">
<resultMap id="roleEntity" type="test.vlog.domian.Role">
<id property="id" column="r_id"></id>
<result property="name" column="r_name"/>
<result property="sort" column="sort"/>
<collection property="users" ofType="test.vlog.domian.User">
<id property="id" column="u_id"></id>
<result property="name" column="u_name"/>
<result property="roleId" column="r_id"/>
<result property="alisa" column="alisa"/>
</collection>
</resultMap>
<select id="getNestRoleList" resultMap="roleEntity">
select r.id as r_id,
r.name as r_name,
r.sort as sort,
u.id as u_id,
u.name as u_name,
u.alisa as alisa
from role r
inner join user u on r.id = u.role_id;
</select>
</mapper>
先看下嵌套ResultMap解析的过程吧:如下图
好的,那我们看下嵌套的ResultMap最终生成的结构吧:
2.3.鉴别器
比如我们有个需求:我们用户表里有name字段和alisa字段,而且有个标志位.当标志位为1时使用name作为用户名,当标志位为2时使用alisa作为用户名.那么通过映射怎么处理呢?
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) DEFAULT NULL COMMENT '角色id',
`name` varchar(40) DEFAULT NULL COMMENT '用户名称',
`alisa` varchar(40) DEFAULT NULL COMMENT '别名',
`tag` tinyint(4) DEFAULT NULL COMMENT '1、使用name,2、使用alisa',
PRIMARY KEY (`id`)
);
public class User {
private Integer id;
private String username;
// getter、setter省略
}
<resultMap id="userEntity" type="test.vlog.domian.User">
<id property="id" column="id"></id>
<discriminator javaType="int" column="tag" >
<case value="1">
<result property="username" column="name" />
</case>
<case value="2">
<result property="username" column="alisa"/>
</case>
</discriminator>
</resultMap>
<select id="getUserList" resultMap="userEntity">
select id,name,alisa,tag from user
</select>
我们先看看鉴别器的处理过程:
最终形成的ResultMap结果,见下图:
好的,三类ResultMap结构的解析过程及之间的数据结构已经分析完毕.那么我们查出来的结果是如何根据这些ResultMap结构进行mysql的列到java类的数据转换呢?这一节我们下期configuration容器(4)–sql映射器–mappedstatement继续分享.喜欢的朋友关注我,且听下回分解.