相关文章
前言
在上篇文章 介绍了 Maper.xml 配置文件的解析,但是没有解析
resultMap
节点,因为该解析比较复杂,也比较难理解,所有单独拿出来进行解析。
在使用
Mybatis
的时候,都会使用
resultMap
节点来绑定列与
bean属性
的对应关系,但是一般就只会使用其简单的属性,他还有一些比较复杂的属性可以实现一些高级的功能,在没查看源码之前,我也只会简单的使用,很多高级的用法都没有使用过,通过这次学习,希望能在工作使用,能够写出简洁高效的SQL。
resultMap的定义
先来看看
resultMap
节点的官方定义:
简单的使用:
resultMap id="userResultMap" type="User"
id property="id" column="user_id" /
result property="username" column="user_name"/
result property="password" column="user_password"/
/resultMap
会把列名和属性名进行绑定,该节点一共有 4 个属性:
**type**
:表示其对应的pojo类型,可以使用别名,也可以使用全限定类名
**extends**
:继承,一个
resultMap
可以继承另一个
resultMap
,这个属性是不是没有用过 ? ^^
接下来看下它可以有哪些子节点:
**id**
– 标识ID列
**association**
– 关联查询
**discriminator**
- 鉴别器:mybatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为
constructor
在查询数据库得到数据后,会把对应列的值赋值给 javabean 对象对应的属性,默认情况下 mybatis 会调用实体类的无参构造方法创建一个实体类,然后再给各个属性赋值,如果没有构造方法的时候,可以使用
constructor
节点进行绑定,如现有如下的构造方法:
public Person(int id, String name, String job, int age) {
this.id = id;
this.name = name;
this.job = job;
this.age = age;
}
则,可以使用
constructor
节点进行绑定:
resultMap id="queryPersonMap" type="mybatis.pojo.Person"
constructor
idArg column="id" javaType="int"/
arg column="name" javaType="string" /
arg column="job" javaType="string" /
arg column="age" javaType="int" /
/constructor
/resultMap
association
关联查询,在级联中有一对一、一对多、多对多等关系,
association
主要是用来解决一对一关系的,
association
可以有多种使用方式:
比如现在有一个
Person
类,它有一个
address
属性,关联
Address
对象:
public class Person implements Serializable {
private int id;
private String name;
private String job;
private int age;
private Address address;
}
public class Address {
private int id;
private String name;
private long number;
}
关联查询方式一:
resultMap id="queryPersonMap" type="mybatis.pojo.Person"
id column="id" property="id"/
result column="name" property="name" /
result column="job" property="job" /
result column="age" property="age"/
association property="address" column="address_id" javaType="mybatis.pojo.Address" select="queryAddress" /
/resultMap
select id="queryAddress" resultType="mybatis.pojo.Address"
select * from address where id = #{id}
/select
关联查询方式二:
resultMap id="queryPersonMap" type="mybatis.pojo.Person"
id column="id" property="id"/
result column="name" property="name" /
result column="job" property="job" /
result column="age" property="age"/
association property="address" column="address_id" javaType="mybatis.pojo.Address" resultMap="addressMap"/
/resultMap
resultMap id="addressMap" type="mybatis.pojo.Address"
id column="id" property="id"/
result column="name" property="name"/
result column="number" property="number"/
/resultMap
关联查询方式三:
resultMap id="queryPersonMap" type="mybatis.pojo.Person"
id column="id" property="id"/
result column="name" property="name" /
result column="job" property="job" /
result column="age" property="age"/
association property="address" javaType="mybatis.pojo.Address"
id column="id" property="id"/
result column="name" property="name"/
result column="number" property="number"/
/association
/resultMap
collection
collection
集合,如果pojo对象有一个属性是集合类型的,可以使用
collection
来进行查询:
public class Person implements Serializable {
private int id;
private String name;
private String job;
private int age;
private ListAddress addressList;
}
collection
使用如下:
resultMap id="queryPersonMap" type="mybatis.pojo.Person"
id column="id" property="id"/
result column="name" property="name" /
result column="job" property="job" /
result column="age" property="age"/
collection property="addressList" javaType="ArrayList" ofType="mybatis.pojo.Address"
id column="id" property="id"/
result column="name" property="name"/
result column="number" property="number"/
/collection
/resultMap
当然还有其他的方法,具体可以参考官网。
discriminator
鉴别器,mybatis可以使用
discriminator
判断某列的值,然后根据某列的值改变封装行为,有点像 Java的
switch
语句,鉴别器指定了
column
和
javaType
属性。 列是 MyBatis 查找比较值的地方。
JavaTyp
e 是需要被用来保证等价测试的合适类型,
比如某列的值等于多少,则返回1,等于多少返回2等等。
resultMap id="vehicleResult" type="Vehicle"
id property="id" column="id" /
result property="vin" column="vin"/
result property="year" column="year"/
result property="make" column="make"/
result property="model" column="model"/
result property="color" column="color"/
discriminator javaType="int" column="vehicle_type"
case value="1" resultMap="carResult"/
case value="2" resultMap="truckResult"/
case value="3" resultMap="vanResult"/
case value="4" resultMap="suvResult"/
/discriminator
/resultMap
以上就是
resultMap
节点的全部使用方法,下面是一个比较复杂的例子,源码解析会按照其来解析,例子来自于官方文档。
resultMap id="detailedBlogResultMap" type="Blog"
constructor
idArg column="blog_id" javaType="int"/
arg column="name" javaType="string" /
/constructor
id column="id" property="id" /
result property="title" column="blog_title"/
association property="author" javaType="Author"
id property="id" column="author_id"/
result property="username" column="author_username"/
/association
collection property="posts" ofType="Post"
id property="id" column="post_id"/
result property="subject" column="post_subject"/
/collection
discriminator javaType="int" column="draft"
case value="1" resultType="DraftPost"/
/discriminator
/resultMap
resultMap 源码解析
首先需要说明的是,一个
resultMap
节点会解析成一个
ResultMap
对象,而每个子节点(除了discriminator节点)会被解析成
ResultMapping
对象,即一个
ResultMap
包含的是
ResultMapping
对象的集合。
先来看看
ResultMapping
的一个声明:
public class ResultMapping {
// configuration 对象
private Configuration configuration;
private String property;
private String column;
private Class? javaType;
private JdbcType jdbcType;
private TypeHandler? typeHandler;
// 对应的是 resultMap 属性,通过id来引用其他的resultMap
private String nestedResultMapId;
// 对应的是 select 属性,通过id来引用其他的select节点的定义
private String nestedQueryId;
private SetString notNullColumns;
private String columnPrefix;
// 处理后的标志,标志有两个 id和constructor
private ListResultFlag flags;
// 对应节点的column属性拆分后生成的结果,composites.size()0会使column为null
private ListResultMapping composites;
private String resultSet;
private String foreignColumn;
private boolean lazy;
}
ResultMap 的声明如下:
public class ResultMap {
// ID,表示一个resultMap
private String id;
// 该resultMap对应的Javabean类型
private Class? type;
// 对应的是除了discriminator节点外的其他节点
private ListResultMapping resultMappings;
// id 节点的映射集合
private ListResultMapping idResultMappings;
// 构造节点的集合
private ListResultMapping constructorResultMappings;
// 记录了映射关系中 不带有contructot节点的的映射关系
private ListResultMapping propertyResultMappings;
// column集合
private SetString mappedColumns;
// discriminator 节点
private Discriminator discriminator;
private boolean hasNestedResultMaps;
private boolean hasNestedQueries;
private Boolean autoMapping;
}
解析:
resultMapElements(context.evalNodes("/mapper/resultMap"));
private void resultMapElements(ListXNode list) throws Exception {
for (XNode resultMapNode : list) {
try {
// 解析每个 resultMap 节点
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
// 注意这里传入的是一个空的集合
return resultMapElement(resultMapNode, Collections.ResultMapping emptyList());
}
主要的解析方法:
private ResultMap resultMapElement(XNode resultMapNode, ListResultMapping additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// ID 属性
String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
// type属性
String type = resultMapNode.getStringAttribute("type",resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType",resultMapNode.getStringAttribute("javaType"))));
// extends 属性
String extend = resultMapNode.getStringAttribute("extends");
// autoMapping 属性
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// 从注册的类型管理器里面查找对应的类型
Class? typeClass = resolveClass(type);
// discriminator 节点
Discriminator discriminator = null;
ListResultMapping resultMappings = new ArrayListResultMapping();
resultMappings.addAll(additionalResultMappings);
// 处理子节点
ListXNode 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 {
ListResultFlag flags = new ArrayListResultFlag();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 处理其他节点,创建 resultMapping 对象并添加到集合中
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
// 创建代表该 resultMap 节点的 ResultMap 对象并添加到 ResultMap 集合中。
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
// 解析失败,添加到集合,重新解析
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
处理 constructor 节点:
private void processConstructorElement(XNode resultChild, Class? resultType, ListResultMapping resultMappings) throws Exception {
ListXNode argChildren = resultChild.getChildren();
for (XNode argChild : argChildren) {
ListResultFlag flags = new ArrayListResultFlag();
// 向集合中添加 contrucator 标志
flags.add(ResultFlag.CONSTRUCTOR);
if ("idArg".equals(argChild.getName())) {
// 添加id标志
flags.add(ResultFlag.ID);
}
// 创建 ResultMapping 对象并添加到集合中
resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
}
}
创建 ResultMapping 对象:
private ResultMapping buildResultMappingFromContext(XNode context, Class? resultType, ListResultFlag flags) throws Exception {
// 解析节点的属性
String 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")
// 对应的 typeHandler 类型
Class? extends TypeHandler? typeHandlerClass = (Class? extends TypeHandler?) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// 创建 ResultMapping 对象
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
之后是创建 ResultMapped 对象并添加到集合中:
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
// 调用的使用 builderAssistant 的 addResultMap 方法
return resultMapResolver.resolve();
public ResultMap addResultMap(String id, Class? type, String extend, Discriminator discriminator, ListResultMapping resultMappings, Boolean autoMapping) {
// 为 id 加上 namespace即 namespace.id
id = applyCurrentNamespace(id, false);
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 resultMap = configuration.getResultMap(extend);
ListResultMapping extendedResultMappings = new ArrayListResultMapping(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) {
IteratorResultMapping extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
extendedResultMappingsIter.remove();
}
}
}
resultMappings.addAll(extendedResultMappings);
}
// 创建 resultMap
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
// 添加到集合
configuration.addResultMap(resultMap);
return resultMap;
}
到这里,就把
resultMap
节点解析完毕了,之后在解析 Mapper.xml 文件的其他节点,参考
原文始发于微信公众号(Java技术大杂烩):