Mybatis Mapper.xml 配置文件中 resultMap 节点的源码解析

 

相关文章

Mybatis 解析配置文件的源码解析

Mybatis 类型转换源码分析

Mybatis 数据源和数据库连接池源码解析(DataSource)

Mybatis Mapper 接口源码解析(binding包)

Mybatis 解析 SQL 源码分析一

前言

在上篇文章 Mybatis 解析 SQL 源码分析一 介绍了 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="hashed_password"/>
</resultMap>

会把列名和属性名进行绑定,该节点一共有 4 个属性:

1. id :表示该 resultMap,共其他的语句调用

2. type:表示其对于的pojo类型,可以使用别名,也可以使用全限定类名

3. autoMapping:如果设置这个属性,MyBatis将会为这个ResultMap开启或者关闭自动映射。这个属性会覆盖全局的属性 autoMappingBehavior。默认值为:unset。

4. extends:继承,一个 resultMap 可以继承另一个 resultMap,这个属性是不是没有用过 ? ^^

接下来看下它可以有哪些子节点:

  • constructor - 用于注入结果到构造方法中
  • id – 标识ID列
  • result – 表示一般列
  • association – 关联查询
  • collection – 查询集合
  • 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 List<Address> addressList;
}
    <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 查找比较值的地方。 JavaType 是需要被用来保证等价测试的合适类型,

比如某列的值等于多少,则返回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 Set<String> notNullColumns;
private String columnPrefix;
// 处理后的标志,标志有两个 id和constructor
private List<ResultFlag> flags;
// 对应节点的column属性拆分后生成的结果,composites.size()>0会使column为null
private List<ResultMapping> 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 List<ResultMapping> resultMappings;
// id 节点的映射集合
private List<ResultMapping> idResultMappings;
// 构造节点的集合
private List<ResultMapping> constructorResultMappings;
// 记录了映射关系中 不带有contructot节点的的映射关系
private List<ResultMapping> propertyResultMappings;
// column集合
private Set<String> mappedColumns;
// discriminator 节点
private Discriminator discriminator;
private boolean hasNestedResultMaps;
private boolean hasNestedQueries;
private Boolean autoMapping;
}

解析:

  resultMapElements(context.evalNodes("/mapper/resultMap"));

  private void resultMapElements(List<XNode> 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, List<ResultMapping> 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;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
// 处理子节点
List<XNode> 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 {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
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, List<ResultMapping> resultMappings) throws Exception {
List<XNode> argChildren = resultChild.getChildren();
for (XNode argChild : argChildren) {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
// 向集合中添加 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, List<ResultFlag> 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, List<ResultMapping> 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);
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
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
// 添加到集合
configuration.addResultMap(resultMap);
return resultMap;
}

Mybatis分页中遇到的坑3的更多相关文章

  1. Mybatis分页中遇到的坑2

    站在巨人的肩膀上    http://crocutax.com/blog/mybatis-one-to-many-nestes-query-and-page-query Mybatis一对多嵌套查询和 ...

  2. Mysql系列八:Mycat和Sharding-jdbc的区别、Mycat分片join、Mycat分页中的坑、Mycat注解、Catlet使用

    一.Mycat和Sharding-jdbc的区别 1)mycat是一个中间件的第三方应用,sharding-jdbc是一个jar包 2)使用mycat时不需要改代码,而使用sharding-jdbc时 ...

  3. Mybatis分页和Spring的集成

    写了一个Mybatis分页控件,在这记录一下使用方式. 在Maven中加入依赖: ? 1 2 3 4 5 6 7 8 9 <dependencies>   ...     <depe ...

  4. mybatis分页插件以及懒加载

    1.   延迟加载 延迟加载的意义在于,虽然是关联查询,但不是及时将关联的数据查询出来,而且在需要的时候进行查询. 开启延迟加载: <setting name="lazyLoading ...

  5. Mybatis分页插件PageHelper正确的用法(网上有2篇不够科学的文章)

    今天下午在Mybatis项目中.实现分页.由于我是后加入项目中的,Leader用的是PageHelper这个组件.可是我在实际使用的过程中遇到了2个大问题. 1.p=2#comments" ...

  6. Mybatis分页插件PageHelper正确的使用方法(网上有2篇不够科学的文章)

    今天下午在Mybatis项目中,实现分页.因为我是后加入项目中的,Leader用的是PageHelper这个组件,但是我在实际使用的过程中遇到了2个大问题. 1.http://www.oschina. ...

  7. Mybatis分页插件PageHelper的配置和使用方法

     Mybatis分页插件PageHelper的配置和使用方法 前言 在web开发过程中涉及到表格时,例如dataTable,就会产生分页的需求,通常我们将分页方式分为两种:前端分页和后端分页. 前端分 ...

  8. Mybatis分页插件PageHelper使用

    一. Mybatis分页插件PageHelper使用  1.不使用插件如何分页: 使用mybatis实现: 1)接口: List<Student> selectStudent(Map< ...

  9. mybatis分页练手

    最近碰到个需求,要做个透明的mybatis分页功能,描述如下:目标:搜索列表的Controller action要和原先保持一样,并且返回的json需要有分页信息,如: @ResponseBody @ ...

随机推荐

  1. android studio发布公共类库到服务器maven仓库

    在上一篇中提到了怎么创建私有maven库,这篇主要结合android studio的使用,直接进入正题,看以下步骤 1.创建android项目 创建Project,然后加一个library的modul ...

  2. centOS7安装docker步骤

    首先准备一台linux系统, Docker需要一个64位系统的系统,内核的版本必须大于3.10,可以用命令来检查是否满足要求: 满足条件后,下面开始正式安装步骤: 1.更新yum: sudo yum ...

  3. 【C++基础学习】数据封装、构造函数

    第一部分 类和对象 内存中按照用途被划分的五个区:栈区.堆区.全局区.常量区.代码区栈区由系统来进行控制,无论是内存的分配还是回收都不需要程序员关心堆区由new分配内存,使用完成之后必须使用delet ...

  4. WSDL文档深入分析

    借助jdk的wsimort.exe工具生成客户端代码 格式:wsimport -keep url   //url为wsdl文件的路径 直接生成客户端代码会抛异常, 无法生成客户端代码, 解决办法: 将 ...

  5. codeforces 的 Codeforces Round #273 (Div. 2) --C Table Decorations

    C. Table Decorations time limit per test 1 second memory limit per test 256 megabytes input standard ...

  6. ping请求超时的解决方法

    我们有时需要进行远程或者共享对方数据库的时候,会ping一下对方电脑,时候能够ping通,时候能够进行数据的传输.有时会出现ping请求超时,那么遇到这个问题该怎么解决? 我们首要解决的是看他自己是否 ...

  7. Redis穿透问题解决方案

    缓存穿透 缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有.这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空.这样请求就绕过缓存直接查数据库,这也是经常提的 ...

  8. codeforces 652C C. Foe Pairs(尺取法+线段树查询一个区间覆盖线段)

    题目链接: C. Foe Pairs time limit per test 1 second memory limit per test 256 megabytes input standard i ...

  9. springMVC之HttpServletRequest的getParameterMap()

    request.getParameterMap()的返回类型是Map类型的对象,也就是符合key-value的对应关系,但这里要注意的是,value的类型是String[],而不是String. 得到 ...

  10. RAutomation 在 Watir中的使用

    RAutomation的用法 require "rautomation" # 通过匹配部分标题来获取窗口 window = RAutomation::Window.new(:tit ...