Mybatis关联查询 一对一查询 一对多查询 多对多查询 discriminator 鉴别器映射

在数据库的增删改查操作中,用的最多的就是查询操作了,查询操作又可以分成一对一查询、一对多查询和多对多查询。一个人属于一个部门,查询人的时候要查出他的部门,这是一对一查询;一辆车有四个轱辘,查询车的时候要查出这四个轱辘,这是一对多查询;一个学生选了多门课,一门课也是被多个学生选的,学生与课程之间用一张关联表来联系,这是多对多查询。

本文介绍 Mybatis 是如何处理这几种查询方式的,包括以下三个部分:

  1. Mybatis 一对一查询
  2. Mybatis 一对多查询
  3. Mybatis 多对多查询
  4. discriminator 鉴别器映射

从代码上来看一对一查询就是在查询 User 对象的时候,查询出他的部门 postion。在 Mybatis 中可以使用 association 来表示一对一的映射关系,并且有嵌套结果嵌套查询两种方式。

 
public class TUser implements Serializable{
    ...
    private TPosition position;

嵌套结果

TUser 表有一个 resultMap 名为 BaseResultMap,配置了 TUser 对象与数据库中字段的映射。

 
<resultMap >
		<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 >
		<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 >
		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 >
		<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 >
		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 >
		<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 >
		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 >
		<collection property="jobs" fetchType="lazy" column="id"
			select="com.enjoylearning.mybatis.mapper.TJobHistoryMapper.selectByUserId" />
</resultMap>

<select >
		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" >
		<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 >
		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 >
		<collection property="healthReports" column="id"
			select= "com.enjoylearning.mybatis.mapper.THealthReportMaleMapper.selectByUserId"></collection>
	</resultMap>
	
	<resultMap >
		<collection property="healthReports" column="id"
			select= "com.enjoylearning.mybatis.mapper.THealthReportFemaleMapper.selectByUserId"></collection>
	</resultMap>
	
	<resultMap >
				 
		<discriminator column="sex"  javaType="int">
			<case value="1" resultMap="userAndHealthReportMale"/>
			<case value="2" resultMap="userAndHealthReportFemale"/>
		</discriminator>
	</resultMap>

<select >
		select
		<include ref />
		from t_user a
</select>