未分类

Mybatis多表联合查询

表与表之间的关系可以分为三种:1-1、1-N、N-N。那么我将用强大的mybatis演示和总结一下这一对多多对多的关系的查询,且分别用两步查询方法和连接查询方法:

准备工作

建立4张表,班主任表、学生表、课程表、选课情况表。班主任和学生对应的是1-N、学生和课程对应的是N-N:

其次,创建对应的3个实体类:

public class Course {
   private Integer id;
   private String name;
   private List<Student> students;//多表查询 多对多
   //get...set...
}

public class Student {
   private Integer id;
   private Integer tid;
   private String name;
   private Teacher teacher; //多表查询 多对一
}

public class Teacher {
   private Integer id;
   private String name;
   private List<Student> students; //多表查询 一对多
}

1-N

两步查询

两步查询比较好理解,就是对持有引用对象的对象执行两次select语句,把属性的对象也查出来。

以下是查询“1-N”的一方,也就是查询老师方,把带的孩子都查询出来:

public List<Teacher> selectAllFromTeacher();
<resultMap id="myMap" type="com.vo.Teacher">
<!--   进行数据库字段与JavaBean字段的映射-->
  <id column="id" property="id"></id>
  <result column="name" property="name"></result>
  <collection property="students" select="com.mapper.IStudent.selectAllByTid" column="id"></collection>
</resultMap>

<select id="selectAllFromTeacher" resultMap="myMap">
   select * from teacher
</select>
   
<select id="selectAllByTid" parameterType="int" resultType="com.vo.Student">
       select * from student where tid = #{id}
</select>

可以看到select语句里没有使用resultType,而是用了resultMap。因为Teacher里有一个属性是student集合,所以可以使用resultMap(结果映射)去把关联的student都映射出来。

resultType用到的子元素有:id(标记出作为 ID 的结果可以帮助提高整体性能)、result(注入到字段或 JavaBean 属性的普通结果)、collection(嵌套结果映射),以下是我学习过程思考了比较久的点:

  • idresult 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。
  • id和result这两个子集的column里的值的就是数据库里的列名字(如果sql语句里为列取了别名,可以替换成别名),property就是上面type里定义的Teacher对象里的属性名。
  • collection子集就特别有意思了,它也是一个集合,而且它是另一个查询方法selectAllByTid查出来的结果集合。它的property就是Teacher对象里的需要多表查询的Students属性啦;column里的id对应的也是数据库列名,但是它也充当着传递给这个嵌套查询的sql语句所需要的参数。所以可以看到selectAllByTid方法里有#{id}。

以下是查询“1-N”的多方,也就是查学生,想把对应的班主任的信息也查出来:

public List<Student> selectAllStu();
<resultMap id="studentMap" type="com.vo.Student">
  <id column="id" property="id"></id>
  <result column="tid" property="tid"></result>
  <result column="name" property="name"></result>
  <association property="teacher" column="tid" select="com.mapper.ITeacher.selectByTid">
  </association>
</resultMap>


<select id="selectAllStu" resultMap="studentMap">
   select * from student
</select>


<select id="selectByTid" parameterType="int" resultMap="myMap">
       select *  from teacher where id = #{tid}
</select>

可以看到,整个代码其实和查询老师的感觉是差不多的。也是在查询学生时,用了属性resultMap来把查询到的所有学生的结果映射下来。唯一的区别就是没有再使用collection,而是使用了association。

association的作用是将数据库中的班主任的信息映射到Student类里的Teacher类属性中。association里的property依然对应的是Student对象的属性名字teacher;column也就是把数据库列名tid的值,也是要传递给嵌套select方法的参数。所以你也可以在selectByTid方法里看见id = #{tid}。就是这么简单!

association和collection

那么为什么一会儿用association,一会儿用collection呢?

  • association用在“has one”的情况,比如上面每个学生都只有一位班主任,所以查询学生表的班主任信息时,用了association去嵌套查询
  • collection用在“has many”的情况,比如上面一位班主任带一个班级的很多学生,那么查询老师信息时,她带的孩子得用collection去嵌套查询

延迟加载

说到分布查询,不得不提的是延迟加载。分步查询是向数据库发送了两条sql语句,但是我们有的时候不需要查询另一个表的消息,而是就查自己表的信息就可以了,所以它还可以使用到延迟懒加载的功能,也就是不去执行另一条嵌套执行语句。

开启延迟加载的方法十分的简单,只需要在mybatis稍微加一下配置一下,并且在resultMap的标签加上fetchType="lazy",这样设置后,只有在测试或者需要调用数据库方法的时候,额外调用getTeacher()方法获取Teacher时,才会去执行例如selectByTid这样的嵌套查询操作!

<settings>
<!-- 延迟加载配置,3.4.5版本默认为false,当为true时<association>标签的fetchType="lazy"无效,要生效必须为false-->
<setting name="aggressiveLazyLoading" value="false" />
</settings>

连接查询

在说联合查询之前,可以先复习一下4种连接查询

  • left join (左连接):返回包括左表中的所有记录和右表中连接字段相等的记录。
  • right join (右连接):返回包括右表中的所有记录和左表中连接字段相等的记录。
  • inner join (等值连接或者叫内连接):只返回两个表中连接字段相等的行。
  • full join (全外连接):返回左右表中所有的记录和左右表中连接字段相等的记录。

例子3

以下是查询一方,也就是查询老师方,把教的学生也都查询出来:

public List<Teacher> selectAll2();
<!--    联合查询-->
<resultMap id="teacher2" type="com.vo.Teacher">
      <id column="tid" property="id"></id>
      <result column="tname" property="name"></result>
<!-- 一对多,查询多方,主表有集合的,一定会有resultMap,且按ofType去封装-->
      <collection property="students" ofType="Student">
          <id column="sid" property="id"></id>
          <result column="sname" property="name"></result>
          <result column="tid" property="tid"></result>
      </collection>
</resultMap>

<!--   左连接-->
  <select id="selectAll2" resultMap="teacher2">
       select t.id tid, t.name tname, s.id sid, s.name sname, tid
           FROM teacher t LEFT JOIN student s ON s.tid = t.id
  </select>

这样的连接查询的用法、写法和分步查询有什么区别呢?

第一肯定是查询的sql语句写成两张表的联合查询形式(我写的例子是左连接)啦!那么第二点,重点看看resultMap里的collection:

  1. 可以看到column里填写的不再是数据库的列名了,而是sql语句里给列名定义的别名了(可以自己修改别名验证一下)。但是property依然对应的Teacher类的属性名字。
  2. collection里不再进行第二次的select查询了,而是直接列名与属性名对应起来。mybatis会帮忙映射结果。
  3. 有一个新的 “ofType” 属性。这个属性非常重要,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。或者在我理解看,用ofType的原因的解释就是Teacher类里有一个属性是List<Student>,它会把查询到的sid、sname、tid结果封装成Student对象。

例子4

以下是查询一对多的多方,也就是查学生,想把老师的信息也查出来:

public List<Student> selectAll();
<!--    左连接查询一对多的多方-->
<select id="selectAll" resultType="com.vo.Student">
  SELECT s.id id , s.name name, tid, t.id `teacher.id` , t.name `teacher.name` FROM student s LEFT JOIN teacher t ON s.tid = t.id
</select>

这个查询中的teacher.id里的teacher啊,切记是Student类里的属性teacher,属性名是啥就写啥不能乱写。由于一个学生相对于一个老师就是一对一的关系,使用这种连接查询的方式,就不需要使用resultMap和resultMap的association啦~~~~一个sql语句就搞得了呢!

N-N

多对多呢,我扯了一个例子,就是多添加了一个课程表,因为一个学生可以选很多课,一门课又可以被很多学生选。同样的,我依然用上面两种方式演示,而且多对多啊其实和一对多没多区别,而且我就演示一种情况就可以了!查询选课情况——每门课的选课学生信息:

分步查询

/**
* 分步查询每门课及选课学生情况
*/
public List<Course> queryCourseStu();
<!-- resultMap:映射实体类和字段之间的一一对应的关系 -->
<resultMap id="studentCourseMap" type="com.vo.Course">
  <id property="id" column="cid" />
  <result property="name" column="cname" />
  <!-- 多对多关联映射:collection -->
  <collection property="students"
              ofType="com.vo.Student"
              column="sid"
               select="com.mapper.IStudent.helpCourse"
  >
  </collection>
</resultMap>

<select id="queryCourseStu" resultMap="studentCourseMap">
   SELECT
  student.id as sid, student.name as sname, course.id as cid, course.name as cname
   FROM student_course sc, student, course
   WHERE sc.sid = student.id AND sc.cid = course.id
</select>

由于是分步查询,所以还需要定义一个把查到的sid去学生表查询学生信息的查询方法:

/**
* 用以选课情况的分步查询
* @return
*/
public List<Student> helpCourse();
<select id="helpCourse" parameterType="int" resultType="Student">
   select id, name from student where id = #{sid}
</select>

连接查询

public interface ICourse {
   /**
    * 查询每门课即选课情况
    * @return
    */
   public List<Course> queryCourseStu();
}
<!-- resultMap:映射实体类和字段之间的一一对应的关系 -->
<resultMap id="studentCourseMap" type="com.vo.Course">
  <id property="id" column="cid" />
  <result property="name" column="cname" />
  <!-- 多对多关联映射:collection -->
  <collection property="students" ofType="com.vo.Student">
      <id property="id" column="sid" />
      <result property="name" column="sname" />
  </collection>
</resultMap>

<!-- sql语句写法1 -->
<select id="queryCourseStu" resultMap="studentCourseMap">
   select
  s.id as sid, s.name as sname, c.id as cid, c.name as cname
   from
  student_course sc
  LEFT OUTER JOIN course c ON c.id = sc.cid
  LEFT OUTER JOIN student s ON s.id = sc.sid
</select>
<!-- sql语句写法2 -->
<select id="queryCourseStu" resultMap="studentCourseMap">
   select
  s.id as sid, s.name as sname, c.id as cid, c.name as cname
   from
  student s,course c,student_course sc
   where s.id=sc.sid
   and c.id=sc.cid
</select>

这里学生和课程就是多对多的关系,所以resultMap里用的就是collection啦~其他的就没什么了~~~啊啊啊啊好开心啊!

参考:

🎈 mybatis官方文档

🎈 MyBatis使用resultMap自定义映射规则与关联映射

🎈 difference-between-collection-and-association

🎈 多对多查询

发表评论

邮箱地址不会被公开。 必填项已用*标注