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值即可,实体类无需任何修改变动。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿洞晓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值