15.复杂查询

本文详细介绍了MyBatis中resultMap如何处理对象间的复杂映射,包括一对一关联(association)和一对多集合(collection)的映射方式,如嵌套Select查询和嵌套结果映射。通过示例展示了如何解决多对一、一对多关系的数据映射问题,以提高数据访问性能并避免N+1查询问题。
摘要由CSDN通过智能技术生成

复杂查询

resultMap解决包含对象的复杂映射

  1. association : 包含对象
  2. collection : 包含对象的列表

关联

idresult 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。

这两者之间的唯一不同是,id 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。

<association property="author" column="blog_author_id" javaType="Author">
  <id property="id" column="author_id"/>  <!--就作用上和result一样-->
  <result property="username" column="author_username"/>
</association>

关联(association)元素处理==“有一个”==类型的关系。 比如,在我们的示例中,一个学生有一个老师。关联结果映射和其它类型的映射工作方式差不多。 你需要指定目标属性名以及属性的javaType(很多时候 MyBatis 可以自己推断出来),在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。

关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:

  • 嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。

  • 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。

association元素属性
属性描述
property映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。 无论是哪一种情形,你都可以使用通常的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
javaType一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcTypeJDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。
typeHandler我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定
关联的嵌套 Select 查询
属性描述
column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
select用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据作为参数传递给目标 select 语句。 具体请参考下面的例子。注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
fetchType可选的。有效值为 lazyeager。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled,使用属性的值。

多对一处理

1.构建数据库多对一关联 (学生对老师)

在这里插入图片描述

2.设计pojo类

Teacher.class

package com.liu.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;
}

Student.class

package com.liu.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private int id;
    private String name;
    //学生需要关联一个老师
    private Teacher teacher ;
}
3.查询所有的学生以及其老师信息
public interface StudentMapper {
    //查询所有学生以及其老师信息
    List<Student> getStudents();
}
<mapper namespace="com.liu.dao.StudentMapper">
    <select id="getStudents" resultType="student">  
        select * from student
    </select>
</mapper>
public List<Student> getStudents() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> t = studentMapper.getStudents();
    sqlSession.close();
    return t;
}
/*
Student(id=1, name=张三, teacher=null)
Student(id=2, name=李四, teacher=null)
Student(id=3, name=王五, teacher=null)
*/

这个时候就会出现问题 : Student类中有一个Teacher属性

而数据表中的tid显然无法在一条语句中新建出一个完整的Teacher对象,因此获取的Student,teacher属性为null

解决方法
1、通过resultMap按照查询进行嵌套处理 嵌套查询
  1. 查询学生的信息 ,包括tid
  2. 根据查出来的tid,继续查对应的Teacher
<mapper namespace="com.liu.dao.StudentMapper">
    <select id="getStudents" resultMap="StudentTeacher">
        select * from student
    </select>
    <select id="getTeacher" resultType="teacher">
        select * from teacher where id = #{tid}
    </select>
    <!--想实现返回一个完整的Student,需要利用resultMap-->
    <resultMap id="StudentTeacher" type="student"> <!--type是返回的类型-->
        <result column="id" property="id"></result>
        <result column="name" property="name"></result>
        <!--复杂的属性,需要单独处理-->
        <!--
        1.对象 association
        2.集合 collection
        -->
        <association column="tid" property="teacher" javaType="teacher" select="getTeacher"></association>
        <!--tid对应的是teacher属性-->
        <!--column:'外键约束' property:'类名映射(teacher) javaType:'对应的全限定类名或类别名' select:'获取映射类的语句 会使用tid来作为getTeacher的参数tid	-->
        <!--
		这条语句的执行顺序是 : 先将column tid传递到getTeacher语句,将取出的数据转化为 javaType teacher类型,作为column tid 的对应属性赋值给返回的studen
		-->
    </resultMap>
</mapper>

结果

/*
Student(id=1, name=张三, teacher=Teacher(id=1, name=张老师))
Student(id=2, name=李四, teacher=Teacher(id=2, name=王老师))
Student(id=3, name=王五, teacher=Teacher(id=1, name=张老师))
*/
2、按照结果进行嵌套处理

先把需要的信息全部都查出来,然后对查出来的属性进行操作

<!--按照结果进行嵌套处理-->
<select id="getUsers2" resultMap="StudentTeacher2">
    select s.id as sid,s.name as sname,t.name as tname from student s,teacher t where s.tid=t.id
</select>
<resultMap id="StudentTeacher2" type="student">
    <result column="sid" property="id"></result>
    <result column="sname" property="name"></result>
    <association property="teacher" javaType="Teacher">
        <result property="name" column="tname"></result>
    </association>
</resultMap>

​ 就是这么简单。我们有两个 select 查询语句:一个用来加载学生(Student),另外一个用来加载老师(Teacher),而且学生的结果映射描述了应该使用 select 语句加载它的teacher属性。

其它所有的属性将会被自动加载,只要它们的列名和属性名相匹配。

这种方式虽然很简单,但在大型数据集或大型数据表上表现不佳。这个问题被称为“N+1 查询问题”。 概括地讲,N+1 查询问题是这样子的:

  • 你执行了一个单独的 SQL 语句来获取结果的一个列表(就是“+1”)。
  • 对列表返回的每条记录,你执行一个 select 查询语句来为每条记录加载详细信息(就是“N”)。

这个问题会导致成百上千的 SQL 语句被执行。有时候,我们不希望产生这样的后果。

好消息是,MyBatis 能够对这样的查询进行延迟加载,因此可以将大量语句同时运行的开销分散开来。 然而,如果你加载记录列表之后立刻就遍历列表以获取嵌套的数据,就会触发所有的延迟加载查询,性能可能会变得很糟糕。

2.嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。
关联的嵌套结果映射
属性描述
resultMap结果映射的 ID,可以将此关联的嵌套结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet。这样的 ResultSet 有部分数据是重复的。 为了将结果集正确地映射到嵌套的对象树中, MyBatis 允许你“串联”结果映射,以便解决嵌套结果集的问题。使用嵌套结果映射的一个例子在表格以后。
columnPrefix当连接多个表时,你可能会不得不使用列别名来避免在 ResultSet 中产生重复的列名。指定 columnPrefix 列名前缀允许你将带有这些前缀的列映射到一个外部的结果映射中。 详细说明请参考后面的例子。
notNullColumn默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。 你可以在这个属性上指定非空的列来改变默认行为,指定后,Mybatis 将只在这些列非空时才创建一个子对象。可以使用逗号分隔来指定多个列。默认值:未设置(unset)。
autoMapping如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。注意,本属性对外部的结果映射无效,所以不能搭配 selectresultMap 元素使用。默认值:未设置(unset)。
<mapper namespace="com.liu.dao.StudentMapper">
    <select id="getStudents2" resultMap="StudentTeacher2">
        select s.id as sid,
               s.name as sname,
               s.tid as stid,
               t.id as tid,
               t.name as tname from student s,teacher t where s.tid=t.id
    </select>
    <resultMap id="StudentTeacher2" type="student">
        <result column="sid" property="id"></result>  <!--student 的 id sid——>id -->
        <result column="sname" property="name"></result> <!--student 的 name sname——>name -->
        <association property="teacher"  column="stid" javaType="teacher" resultMap="getTeacher"> 
   			<!--student 的 stid(student表中的列名)——>teacher属性
			javaType 表明这里映射的是Teacher类(核心配置文件采取包名,默认首字母小写)
			resultMap 提示程序到getTeacher这个映射中去构建所需的Teacher类
			-->
        </association>
    </resultMap>
        <!--getTeacher是一个针对被包含对象teacher的映射-->
    <resultMap id="getTeacher" type="teacher">  <!--type=teacher 返回一个teacher类型-->
    <id column="tid" property="id"></id> <!--teacher的 id  tid——>id -->
    <result column="tname" property="name"></result> <!--teacher的 name tname——>name-->
    </resultMap>
</mapper>

可以简化为 :

<mapper namespace="com.liu.dao.StudentMapper">
    <select id="getStudents2" resultMap="StudentTeacher2">
        select s.id as sid,
               s.name as sname,
               s.tid as stid,
               t.id as tid,
               t.name as tname from student s,teacher t where s.tid=t.id
    </select>
    <resultMap id="StudentTeacher2" type="student">
        <result column="sid" property="id"></result>  <!--student 的 id sid——>id -->
        <result column="sname" property="name"></result> <!--student 的 name sname——>name -->
        <association property="teacher" javaType="teacher"> 
            <id property="id" column="tid"></id>
            <result property="name" column="tname"></result>
        </association>
    </resultMap>
</mapper>

上面的示例使用了外部的结果映射元素来映射关联。这使得 Student、Teacher 的结果映射可以被重用,不必再去重新查表,节约了资源

一对多处理

集合
<collection property="posts" ofType="domain.blog.Post">
  <id property="id" column="post_id"/>
  <result property="subject" column="post_subject"/>
  <result property="body" column="post_body"/>
</collection>

集合元素和关联元素几乎是一样的,它们相似的程度之高,以致于没有必要再介绍集合元素的相似部分。 所以让我们来关注它们的不同之处吧。

我们来继续上面的示例,一个博客(Blog)只有一个作者(Author)。但一个博客有很多文章(Post)。 在博客类中,这可以用下面的写法来表示:

private List<Post> posts;

要像上面这样,映射嵌套结果集合到一个 List 中,可以使用集合元素。 和关联元素一样,我们可以使用嵌套 Select 查询,或基于连接的嵌套结果映射集合。

集合的嵌套 Select 查询

首先,让我们看看如何使用嵌套 Select 查询来为博客加载文章。

<resultMap id="blogResult" type="Blog">
  <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectPostsForBlog" resultType="Post">
  SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>

你可能会立刻注意到几个不同,但大部分都和我们上面学习过的关联元素非常相似。 首先,你会注意到我们使用的是集合元素。 接下来你会注意到有一个新的 ofType 属性。这个属性非常重要,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。 所以你可以按照下面这样来阅读映射:

<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>

读作: “posts 是一个存储 Post 的 ArrayList 集合”

在一般情况下,MyBatis 可以推断 javaType 属性,因此并不需要填写。所以很多时候你可以简略成:

<collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>
集合的嵌套结果映射

现在你可能已经猜到了集合的嵌套结果映射是怎样工作的——除了新增的 “ofType” 属性,它和关联的完全相同。

首先, 让我们看看对应的 SQL 语句:

<select id="selectBlog" resultMap="blogResult">
  select
  B.id as blog_id,
  B.title as blog_title,
  B.author_id as blog_author_id,
  P.id as post_id,
  P.subject as post_subject,
  P.body as post_body,
  from Blog B
  left outer join Post P on B.id = P.blog_id
  where B.id = #{id}
</select>

我们再次连接了博客表和文章表,并且为每一列都赋予了一个有意义的别名,以便映射保持简单。 要映射博客里面的文章集合,就这么简单:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <result property="body" column="post_body"/>
  </collection>
</resultMap>

再提醒一次,要记得上面 id 元素的重要性,如果你不记得了,请阅读关联部分的相关部分。

如果你喜欢更详略的、可重用的结果映射,你可以使用下面的等价形式:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>

<resultMap id="blogPostResult" type="Post">
  <id property="id" column="id"/>
  <result property="subject" column="subject"/>
  <result property="body" column="body"/>
</resultMap>
1.构建数据库多对一关联 (老师对学生)

在这里插入图片描述

2.设计pojo类

Teacher.class

package com.liu.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;
    //Teacher表达一对多的关系 可以设置一个 List<Student>
    private List<Student> students;
}

Student.class

package com.liu.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private int id;
    private String name;
    private int tid;
}
3.查询所有的老师信息
1.嵌套Select查询
<mapper namespace="com.liu.dao.TeacherMapper">
    <select id="getTeacher" resultMap="TeacherStudent">
        select s.id as sid,
               s.name as sname,
               t.name as tname,
               t.id as tid from student s,teacher t where s.id = t.id and t.id=#{tid}
    </select>
    <resultMap id="TeacherStudent" type="teacher">  <!--映射类型为teacher(别名,使用了package映射)-->
        <id property="id" column="tid"></id>   
        <result property="name" column="tname"></result>
        <!--集合使用collection,ofType来获取泛型的类型-->
        <collection property="students" ofType="student" javaType="ArrayList" column="sid" select="getStudentList"/> 
        <!--
		1.Teacher的students属性
		2.ofType表明 泛型是 Student(别名student) 
		3.javaType表明 映射类型是 ArrayList
		4.column 表明要在子select查询中的参数 sid
		5.select 表示子查询语句
		-->
    </resultMap>
    <select id="getStudentList" resultType="student">
        select * from student where id=#{sid}  <!--使用resultMap中的sid-->
    </select>
</mapper>
//结果
Teacher(id=1, name=张老师, students=[Student(id=1, name=张三, tid=1)])
2.嵌套结果映射
<mapper namespace="com.liu.dao.TeacherMapper">
    <select id="getTeacher" resultMap="TeacherStudent">
        select s.id as sid,
               s.name as sname,
               t.name as tname,
               t.id as tid from student s,teacher t where s.id = t.id and t.id=#{tid}
    </select>
    <resultMap id="TeacherStudent" type="teacher">
        <id property="id" column="tid"></id>
        <result property="name" column="tname"></result>
        <!--集合使用collection,ofType来获取泛型的类型-->
        <collection property="students" ofType="student" javaType="ArrayList" resultMap="getStudentList"/>
    </resultMap>
    <!--一个复用了最上方别名的student的resultMap-->
    <resultMap id="getStudentList" type="student">
        <id column="sid" property="id"></id>
        <result column="sname" property="name"></result>
    </resultMap>
</mapper>

简化版

<resultMap id="TeacherStudent" type="teacher">
    <id property="id" column="tid"></id>
    <result property="name" column="tname"></result>
    <!--集合使用collection,ofType来获取泛型的类型-->
    <collection property="students" ofType="student" javaType="ArrayList">
        <id column="sid" property="id"></id>
        <result column="sname" property="name"></result>
    </collection>
</resultMap>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值