在数据库的增删改查操作中,用的最多的就是查询操作了,查询操作又可以分成一对一查询、一对多查询和多对多查询。一个人属于一个部门,查询人的时候要查出他的部门,这是一对一查询;一辆车有四个轱辘,查询车的时候要查出这四个轱辘,这是一对多查询;一个学生选了多门课,一门课也是被多个学生选的,学生与课程之间用一张关联表来联系,这是多对多查询。
本文介绍 Mybatis 是如何处理这几种查询方式的,包括以下三个部分:
- Mybatis 一对一查询
- Mybatis 一对多查询
- Mybatis 多对多查询
- discriminator 鉴别器映射
一对一查询
从代码上来看一对一查询就是在查询 User 对象的时候,查询出他的部门 postion。在 Mybatis 中可以使用 association
来表示一对一的映射关系,并且有嵌套结果和嵌套查询两种方式。
|
public class TUser implements Serializable{
...
private TPosition position;
|
嵌套结果
TUser 表有一个 resultMap 名为 BaseResultMap,配置了 TUser 对象与数据库中字段的映射。
|
<resultMap id="BaseResultMap" type="TUser">
<id column="id" property="id" />
<result column="user_name" property="userName" />
<result column="real_name" property="realName" />
<result column="sex" property="sex" />
<result column="mobile" property="mobile" />
<result column="email" property="email" />
<result column="note" property="note" />
</resultMap>
|
我们现在要一对一查询 TUser 所在的部门 Postion,就新建一个 resultMap 叫做 userAndPosition1。它 extends
自 BaseResultMap,拥有其所有的变量。我们查询的是 TUser,同时一对一查询出 postion,所以 userAndPosition1 的类型是 TUser。
|
<resultMap id="userAndPosition1" extends="BaseResultMap" type="TUser">
<association property="position" javaType="TPosition" columnPrefix="post_">
<id column="id" property="id"/>
<result column="name" property="postName"/>
<result column="note" property="note"/>
</association>
</resultMap>
|
这里 association
帮助我们配置映射关系,它有这样几个常用属性:
- property 表示实体类中的属性名,也就是我们要关联查询的 postion
- javaType 表示这个属性对应的类型,postion 对应的类型是 TPostion
- resultMap 可以使用现有的 resultMap 而不重新配置映射关系,这里进行手动配置
- columnPrefix 是查询列的前缀,配置字段的时候就不需要加上前缀,只在查询语句中加入前缀即可
关联表查询的时候加上前缀是很好的编程习惯,因为当关联的多张表中有字段重复的时候,前缀可以帮助我们区分这些字段。
|
<select id="selectUserPosition1" resultMap="userAndPosition1">
select
a.id,
user_name,
real_name,
sex,
mobile,
email,
a.note,
b.id post_id,
b.post_name,
b.note post_note
from t_user a,
t_position b
where a.position_id = b.id
</select>
|
嵌套查询
嵌套结果是一次关联查询多张变,一次性返回结果。嵌套查询则是先完成主查询,再将朱查询中列的结果作为嵌套查询的参数,通过子查询获得关联的对象。
|
<resultMap id="userAndPosition2" extends="BaseResultMap" type="TUser">
<association property="position" fetchType="lazy" column="position_id"
select="com.enjoylearning.mybatis.mapper.
TPositionMapper.selectByPrimaryKey" />
</resultMap>
|
任然是使用 association
来表示关联关系:
- property 表示实体类中的属性名,也就是我们要关联查询的 postion
- column 是主查询中的列名,也是子查询的参数
- fetchType 是数据加载的方式,可选值为 lazy 和 eager,分别为延迟加载和积极加载 ,这个配置会覆盖全局的 lazyLoadingEnabled 配置;
- select 是查询语句的 id,指定 mapper 文件中的查询语句
|
<select id="selectUserPosition2" resultMap="userAndPosition2">
select
a.id,
a.user_name,
a.real_name,
a.sex,
a.mobile,
a.position_id
from t_user a
</select>
|
「N+1 查询问题」:嵌套查询中,主查询会返回 1 个结果列表,子查询根据这个列表中的关联字段进行 N 次查询,如果主查询获取的结果很多就会执行成百上千次的 SQL 语句,这就是 「N+1查询问题」。
同时进行成百上千次的查询落在数据库上,会影响系统的性能,这是我们不希望见到的。解决办法是在 association 中使用fetchType=lazy开启懒加载,但是全局 setting 禁用懒加载<setting name="aggressiveLazyLoading" value="false"/>
。懒加载即只有在使用到这个字段的时候才会执行 SQL,这样就可以减少 「N+1查询问题」。
一对多查询
一对一查询主要主要 association
标签实现,一对多查询则使用 collection
标签实现。
|
public class TUser implements Serializable{
...
// 一对一查询,一个人一个职位
private TPosition position;
// 一对多查询,一个人多次工作经历
private List<TJobHistory> jobs ;
|
嵌套结果
|
<resultMap id="userAndJobs1" extends="BaseResultMap" type="TUser">
<collection property="jobs"
ofType="com.enjoylearning.mybatis.entity.TJobHistory" >
<result column="comp_name" property="compName" jdbcType="VARCHAR" />
<result column="years" property="years" jdbcType="INTEGER" />
<result column="title" property="title" jdbcType="VARCHAR" />
</collection>
</resultMap>
<select id="selectUserJobs1" resultMap="userAndJobs1">
select
a.id,
a.user_name,
a.real_name,
a.sex,
a.mobile,
b.comp_name,
b.years,
b.title
from t_user a,
t_job_history b
where a.id = b.user_id
</select>
|
嵌套查询
|
<resultMap id="userAndJobs2" extends="BaseResultMap" type="TUser">
<collection property="jobs" fetchType="lazy" column="id"
select="com.enjoylearning.mybatis.mapper.TJobHistoryMapper.selectByUserId" />
</resultMap>
<select id="selectUserJobs2" resultMap="userAndJobs2">
select
a.id,
a.user_name,
a.real_name,
a.sex,
a.mobile
from t_user a
</select>
|
多对多查询
多对多由两个一对多关系组成的,需要一种中间表建立连接关系。比如说 t_user 和 t_role 之间是通过 t_user_role 这张表来关联的。
|
public class TUser implements Serializable {
private List<TRole> roles;
|
在一对多查询这一节中,t_user 与 t_job 通过用户的 id 可以直接关联起来。在多对多的场景中,用户和角色则需要通过中间表来关联,SQL 语句就变成了这样:
|
<resultMap type="TUser" id="userRoleInfo" extends="BaseResultMap">
<collection property="roles" ofType="TRole" columnPrefix="role_">
<result column="id" property="id" />
<result column="Name" property="roleName" />
<result column="note" property="note" />
</collection>
</resultMap>
<select id="selectUserRole" resultMap="userRoleInfo">
select a.id,
a.user_name,
a.real_name,
a.sex,
a.mobile,
a.note,
b.role_id,
c.role_name,
c.note role_note
from t_user a,
t_user_role b,
t_role c
where a.id = b.user_id AND
b.role_id = c.id
</select>
|
discriminator 鉴别器映射
通过应用场景来理解:比如说有一个订单表,表中有一个字段用来标记订单的种类,1是机票订单、2是酒店订单。使用鉴别器映射可以在标记字段不同的情况下,分别执行不同的 SQL 语句,查询出不同的结果。
在特定的情况下使用不同的pojo进行关联, 鉴别器元素就是被设计来处理这个情况的。鉴别器非常容易理解,因为它的表现很像 Java 语言中的 switch 语句;
- discriminator 标签常用的两个属性如下:
- column:该属性用于设置要进行鉴别比较值的列 。
- javaType:该属性用于指定列的类型,保证使用相同的 Java 类型来比较值。
- discriminator 标签可以有1个或多个 case 标签, case 标签包含以下三个属性 。
- value : 该值为 discriminator 指定 column 用来匹配的值 。
- resultMap : 当column的值和value的值匹配时,可以配置使用resultMap指定的映射,resultMap优先级高于 resultType 。
- resultType : 当 column 的值和 value 的值匹配时,用于配置使用 resultType指定的映射。
|
<resultMap id="userAndHealthReportMale" extends="userAndHealthReport" type="TUser">
<collection property="healthReports" column="id"
select= "com.enjoylearning.mybatis.mapper.THealthReportMaleMapper.selectByUserId"></collection>
</resultMap>
<resultMap id="userAndHealthReportFemale" extends="userAndHealthReport" type="TUser">
<collection property="healthReports" column="id"
select= "com.enjoylearning.mybatis.mapper.THealthReportFemaleMapper.selectByUserId"></collection>
</resultMap>
<resultMap id="userAndHealthReport" extends="BaseResultMap" type="TUser">
<discriminator column="sex" javaType="int">
<case value="1" resultMap="userAndHealthReportMale"/>
<case value="2" resultMap="userAndHealthReportFemale"/>
</discriminator>
</resultMap>
<select id="selectUserHealthReport" resultMap="userAndHealthReport">
select
<include refid="Base_Column_List" />
from t_user a
</select>
|