ORM框架之Mybatis(四):映射文件resultMap标签详解

Mybatis的映射文件中顶级的标签并不多,之前有说过selectupdatedeleteinsertsql等标签,resultMap在之前的文章也有提过,但是当时也是简单的提过,其实这个标签里面的内容很多,可简单可复杂。正常开发中简单的基本都在使用,但是涉及到复杂的用的就少啦。

association实现一对一的查询,collection实现一对多的查询,discriminator实现鉴别器的功能,也就是根据不同的条件实现不同的关联查询。为什么说用到这些复杂的内容很少呢。以前在项目上性能要求不是很高,对分库分表概念没有那么高的时候,采用关联查询其实没有什么不好,但是现在互联网发展迅速,数据量变得庞大,查询SQL的性能要求也变得更高。关联查询在大数据量的时候执行效率不高也就格外的凸显出来了。如果看过阿里的Java开发手册可以知道,他们是不推荐关联查询,而是提倡单表操作,如果需要查其他表信息,就根据条件,操作其他单表。

虽然关联查询操作变少,但是面试的时候还是可以用来问的。不管从哪一个方面来说扩展知识面都不是坏事。

resultMap子标签之association标签

在说association标签的具体使用前,先说一下两种关联查询方式。

  • 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集
  • 嵌套查询:通过执行另外一个 SQL 映射语句来返回预期的复杂类型

咋一看上面的两个概念很难理解,下面用代码体现一下。

嵌套结果方式
//用户类
public class User{
    private Integer id;
    private String user_name;
    private String phone;
    private String email;
    private Father father;
}
//父亲类
public class Father{
    private Integer id;
    private String name;
    private Integer age;
}
<!--用户表基础字段的映射关系-->
<resultMap id="userBaseColumnMap" type="com.zdydoit.core.model.User">
    <id property="id" column="id"/>
    <result property="userName" column="user_name"/>
    <result property="phone" column="phone"/>
    <result property="email" column="email"/>
</resultMap>
<!-- 嵌套结果方式 -->
<resultMap id="selectUserMap1" extends="userBaseColumnMap" type="com.zdydoit.core.model.User">
    <association property="father" javaType="com.zdydoit.core.model.Father" columnPrefix="tf_">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
    </association>
</resultMap>
<!--查询语句-->
<select id="slectUser1" resultMap="selectUserMap1" parameterType="java.lang.Integer">
    select
    tu.id,
    tu.user_name,
    tu.phone,
    tu.email,
    tu.father_id,
    tf.id tf_id,
    tf.name tf_name,
    tf.age tf_age
    from t_user tu,t_father tf
    where tu.id = #{id} and tu.father_id = tf.id
</select>

用户表中有一个字段father,对应的是Father实体类,每人都有一个父亲,而且是一对一的关系,至于那些大干爹、二干爹这里不做考虑。查询出来的结果用户信息的字段自然封装到User中,那父亲的信息就是放在father字段中。

查询语句对应的resultMapselectUserMap1selectUserMap1是继承userBaseColumnMap,这里继承可以理解成java里面的继承,也就是说继承了父类的字段映射关系。重点看的是association标签,property属性对应的是User类中的father字段,表示association内的查询结果是封装在father字段中,对应的javaType自然就是FathercolumnPrefix表示字段的前缀,这里加上前缀是很有必要的,因为在两个表做关联的时候,可能会存在数据库中字段名相同,那么在这里如果不用别名,就会导致两个字段映射过程混乱。

嵌套查询方式

这种方式需要关联到另一个命名空间,上面的例子是在同一个命名空间com.zdydoit.core.mapper.UserMapper下。这里的另一个命名空间就是com.zdydoit.core.mapper.FatherMapper

实体类的代码和上面的一样,下面只做映射关系的展示。

com.zdydoit.core.mapper.UserMapper命名空间下:

<!-- 嵌套查询方式 -->
<resultMap id="selectUserMap2" type="com.zdydoit.core.model.User">
    <association property="father" column="father_id" select="com.zdydoit.core.mapper.FatherMapper.selectById"/>
</resultMap>
<!-- 查询语句 -->
<select id="selectUser2" resultMap="selectUserMap2" parameterType="java.lang.Integer">
    select
    id,
    user_name,
    phone,
    email,
    father_id
    from t_user
    where id = #{id}
</select>

com.zdydoit.core.mapper.FatherMapper命名空间下:

<resultMap id="fatherBaseColumnMap" type="com.zdydoit.core.model.Father">
    <id property="id" column="id"/>
    <result property="name" column="name" />
    <result property="age" column="age" />
</resultMap>
<!-- 单表查询 -->
<select id="selectById" resultMap="fatherBaseColumnMap">
    select
    id,
    name,
    age
    from t_father
    where id = #{father_id}
</select>

这样查询的过程稍微有点绕。首先是对t_user表执行单表查询操作,查询的结果中有一个字段father_id,然后就是association标签将这个字段作为条件传给com.zdydoit.core.mapper.FatherMapper命名空间,执行另一个单表查询操作selectById查询Father的信息。和之前的区别就是,之前是一个SQL执行关联查询,现在是用多个单表查询组合得到查询结果。

这里只有一个查询条件father_id,如果有多个写法就按下面的写法。

<!-- 
这里旨在说明column多参数的写法
'rel_'开头的值表示SQL语句里面查询出的字段名
等号左边的表示在selectInfo中引用的字段名称 
-->
<association property = "father" colum="{id = rel_id,age = rel_age,nickname = rel_nickname}" select="com.zdydoit.mapper.XxxMapper.selectInfo">

association标签的属性很多,下面总结一下常用标签的含义。

  • property属性,这个属性是必须的,对应的是实体类中的字段,和result标签的property属性是相同含义的。
  • resultMap属性,association标签内也有idreuslt标签,用来做关联类的字段映射关系,当然这个关系也可以通过一个reusltMap表示,省去association内写过多的映射关系。这种reusltMap指定可以跨命名空间。
  • column属性,在嵌套查询方式的时候使用,用来给嵌套语句查询传递条件,可以传递多个。
  • columnPrefix属性,在嵌套结果方式的时候使用,用来指定别名字段的前缀,可用于防止两个表结果字段重名问题。
  • select属性,在嵌套查询方式的时候使用,用来指定嵌套语句查询的具体坐标。
  • fetchType属性,这个属性有两个可选值,分别是lazyeager,分别表示是否懒加载,如果设置为懒加载,在主类中没有使用到这个关联对象,就会少一次关联字句查询的过程,只有使用的时候才去查询。
  • javaType属性,指定关联对象的具体类型。

resultMap子标签之collection标签

collectionassociation标签的使用方式是相同的,也包含两种关联查询方式,唯一不同的就是association只能对应一条记录,是一对一的关系,使用单个类字段来接收,collection可以对应多条记录,是一对多的关系,接收使用集合方式。

还有就是collection多了一个ofType字段,这个字段是用来指定集合内元素的类型,如下:

private List<Computer> computers;

这个是有ofType对应的类型就是Computer。同样理解javaType对应的类型就是List。不过这里一般都不会手动去指定javaType,会自动映射到集合中。

resultMap子标签之discriminator标签

这个应用场景感觉不多,不过还是可以举个栗子理解一下的。

在做体检的时候,男性和女性体检内容是有区别的。现在有三张表,分别是人员表、男性体检报告表、女性体检报告表。要求查一个人的体检报告。那很简单是当这个人是男性的时候,查询其体检报告是从男性体检报告表中查询,反之就到女性体检报告表中查询。实现方式如下:

<!-- 人员基础信息 -->
<resultMap id="personBaseColumnMap" type="Person">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="gender" column="gender"/>
</resultMap>

<!-- 查询女性体检报告 -->
<resultMap id="healthReportFemaleMap" extends="personReportMap" type="Person">
    <collection property="healthReports" column="id" select="com.zdydoit.mapper.HealthFemaleMapper.selectByPersonId"/>
</resultMap>

<!-- 查询男性体检报告 -->
<resultMap id="healthReportMaleMap" extends="personReportMap" type="Person">
    <collection property="healthReports" column="id" select="com.zdydoit.mapper.HealthMaleMapper.selectByPersonId"/>
</resultMap>

<!-- 查询人员信息+体检报告 -->
<resultMap id="personReportMap" extends="personBaseColumnMap" type="Person">
    <discriminator javaType="java.lang.Integer" column="gender">
        <case value="1" resultMap="healthReportMaleMap"/>
        <case value="2" resultMap="healthReportFemaleMap"/>
    </discriminator>
</resultMap>

<!-- 查询语句 -->
<select id="selectPersonReport" resultMap="personReportMap" parameterType="java.lang.Integer">
    select id,name,gender from t_person where id = #{id}
</select>
    

有几点需要说一下。

  • case标签里面结果涉及到两个报告类型分别是HealthReportMaleHealthReportFemale,但是Person类中可以定义两个字段分别接收这两种,也可以用一个字段接收,这个接收字段集合的元素类型应该定义一个父类HealthReport,让HealthReportMaleHealthReportFemale都继承这个父类。
  • case标签内有resultTyperesultMap两种指定结果的方式,这里使用的都是resultMap的方式,因为这种方式更优。如果使用resultType,只能使用嵌套结果方式,然后又涉及到多张不同的表,代码量变大,而且是重复的代码量。这里稍微有点难理解,可以上手写一下就知道了。
  • resultMap的继承关系,这里涉及到多个resultMapselect标签指定的是personReportMap,是直接resultMappersonReportMap继承personBaseColumnMap,因为人员基础信息映射关系都在这个personBaseColumnMap中,然后case内的healtReportMaleMaphealthReportFemaleMap都是继承personReportMap

Overview

习惯性的总结一下。

  • 说了两种关联查询方式,分别是嵌套结果和嵌套查询,贯穿了三个标签的使用。
  • association标签用于一对一的关联查询,主要属性也做了说明。
  • collection标签用于一对多的关联查询,没有具体说,可以参考association标签的解释,基本相同。
  • discriminator标签,使用的过程最为复杂,主要的是要理清resultMap的继承关系。
  • 这些标签在实际开发中很少用到,现在都提倡单表操作,简单高效,兼容分库分表等。因此只做了解,扫知识盲点,提高面试底气。但是resultMap标签的基本使用是简单的,而且是极其重要的。

resultMap标签是很重要的,甚至可以说是极其的重要,但是这些关联查询的功能标签不是很重要,这个是不相悖的。

resultMap有个很大的优点就是解耦合(阿里Java手册也着重强调)。如果使用自动映射的话,会将数据库列表和实体类的字段名强耦合,当数据库列表发生修改时,实体类中的字段名也要同步修改。但是使用resultMap指定映射关系,当有数据库列名发生变化,只要改一下映射中的column值即可,实体类无需任何修改变动。

本文作者:程序猿杨鲍
版权归作者所有,转载请注明出处

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,039评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,426评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,417评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,868评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,892评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,692评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,416评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,326评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,782评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,957评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,102评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,790评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,442评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,996评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,113评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,332评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,044评论 2 355

推荐阅读更多精彩内容

  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,523评论 0 4
  • title: MyBatis之使用resultMap实现高级映射tags: MyBatiscategories: ...
    codingXiaxw阅读 1,526评论 1 1
  • MyBatis 理论篇 [TOC] 什么是MyBatis  MyBatis是支持普通SQL查询,存储过程和高级映射...
    有_味阅读 2,904评论 0 26
  • SQL 映射文件的顶级元素(按照它们应该被定义的顺序): cache – 给定命名空间的缓存配置。 cache-r...
    悠扬前奏阅读 708评论 0 0
  • 周末上班,带着大宝贝值班,我想让他在我单位安安静静的做作业,抽空中午带孩子出去玩一趟,这两天和宝贝关系不是很好,我...
    爱你的贝贝阅读 323评论 3 8