Mybatis的映射文件中顶级的标签并不多,之前有说过select
、update
、delete
、insert
、sql
等标签,resultMap
在之前的文章也有提过,但是当时也是简单的提过,其实这个标签里面的内容很多,可简单可复杂。正常开发中简单的基本都在使用,但是涉及到复杂的用的就少啦。
association
实现一对一的查询,collection
实现一对多的查询,discriminator
实现鉴别器的功能,也就是根据不同的条件实现不同的关联查询。为什么说用到这些复杂的内容很少呢。以前在项目上性能要求不是很高,对分库分表概念没有那么高的时候,采用关联查询其实没有什么不好,但是现在互联网发展迅速,数据量变得庞大,查询SQL的性能要求也变得更高。关联查询在大数据量的时候执行效率不高也就格外的凸显出来了。如果看过阿里的Java开发手册可以知道,他们是不推荐关联查询,而是提倡单表操作,如果需要查其他表信息,就根据条件,操作其他单表。
虽然关联查询操作变少,但是面试的时候还是可以用来问的。不管从哪一个方面来说扩展知识面都不是坏事。
1. resultMap子标签之association标签
在说association
标签的具体使用前,先说一下两种关联查询方式。
- 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集
- 嵌套查询:通过执行另外一个 SQL 映射语句来返回预期的复杂类型
咋一看上面的两个概念很难理解,下面用代码体现一下。
1.1 嵌套结果方式
//用户类
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
字段中。
查询语句对应的resultMap
是selectUserMap1
,selectUserMap1
是继承userBaseColumnMap
,这里继承可以理解成java里面的继承,也就是说继承了父类的字段映射关系。重点看的是association
标签,property
属性对应的是User
类中的father
字段,表示association
内的查询结果是封装在father
字段中,对应的javaType
自然就是Father
,columnPrefix
表示字段的前缀,这里加上前缀是很有必要的,因为在两个表做关联的时候,可能会存在数据库中字段名相同,那么在这里如果不用别名,就会导致两个字段映射过程混乱。
1.2 嵌套查询方式
这种方式需要关联到另一个命名空间,上面的例子是在同一个命名空间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
标签内也有id
、reuslt
标签,用来做关联类的字段映射关系,当然这个关系也可以通过一个reusltMap
表示,省去association
内写过多的映射关系。这种reusltMap
指定可以跨命名空间。column
属性,在嵌套查询方式的时候使用,用来给嵌套语句查询传递条件,可以传递多个。columnPrefix
属性,在嵌套结果方式的时候使用,用来指定别名字段的前缀,可用于防止两个表结果字段重名问题。select
属性,在嵌套查询方式的时候使用,用来指定嵌套语句查询的具体坐标。fetchType
属性,这个属性有两个可选值,分别是lazy
和eager
,分别表示是否懒加载,如果设置为懒加载,在主类中没有使用到这个关联对象,就会少一次关联字句查询的过程,只有使用的时候才去查询。javaType
属性,指定关联对象的具体类型。
2. resultMap子标签之collection标签
collection
和association
标签的使用方式是相同的,也包含两种关联查询方式,唯一不同的就是association
只能对应一条记录,是一对一的关系,使用单个类字段来接收,collection
可以对应多条记录,是一对多的关系,接收使用集合方式。
还有就是collection
多了一个ofType
字段,这个字段是用来指定集合内元素的类型,如下:
private List<Computer> computers;
这个是有ofType
对应的类型就是Computer
。同样理解javaType
对应的类型就是List
。不过这里一般都不会手动去指定javaType
,会自动映射到集合中。
3. 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
标签里面结果涉及到两个报告类型分别是HealthReportMale
和HealthReportFemale
,但是Person
类中可以定义两个字段分别接收这两种,也可以用一个字段接收,这个接收字段集合的元素类型应该定义一个父类HealthReport
,让HealthReportMale
和HealthReportFemale
都继承这个父类。case
标签内有resultType
和resultMap
两种指定结果的方式,这里使用的都是resultMap
的方式,因为这种方式更优。如果使用resultType
,只能使用嵌套结果方式,然后又涉及到多张不同的表,代码量变大,而且是重复的代码量。这里稍微有点难理解,可以上手写一下就知道了。resultMap
的继承关系,这里涉及到多个resultMap
,select
标签指定的是personReportMap
,是直接resultMap
,personReportMap
继承personBaseColumnMap
,因为人员基础信息映射关系都在这个personBaseColumnMap
中,然后case
内的healtReportMaleMap
和healthReportFemaleMap
都是继承personReportMap
。
4. 总结
- 说了两种关联查询方式,分别是嵌套结果和嵌套查询,贯穿了三个标签的使用。
association
标签用于一对一的关联查询,主要属性也做了说明。collection
标签用于一对多的关联查询,没有具体说,可以参考association
标签的解释,基本相同。discriminator
标签,使用的过程最为复杂,主要的是要理清resultMap
的继承关系。- 这些标签在实际开发中很少用到,现在都提倡单表操作,简单高效,兼容分库分表等。因此只做了解,扫知识盲点,提高面试底气。但是
resultMap
标签的基本使用是简单的,而且是极其重要的。
resultMap
标签是很重要的,甚至可以说是极其的重要,但是这些关联查询的功能标签不是很重要,这个是不相悖的。
resultMap
有个很大的优点就是解耦合(阿里Java手册也着重强调)。如果使用自动映射的话,会将数据库列表和实体类的字段名强耦合,当数据库列表发生修改时,实体类中的字段名也要同步修改。但是使用resultMap
指定映射关系,当有数据库列名发生变化,只要改一下映射中的column
值即可,实体类无需任何修改变动。