表与表之间的关系可以分为三种: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(嵌套结果映射),以下是我学习过程思考了比较久的点:
- id 和 result 元素都将一个列的值映射到一个简单数据类型(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:
- 可以看到column里填写的不再是数据库的列名了,而是sql语句里给列名定义的别名了(可以自己修改别名验证一下)。但是property依然对应的Teacher类的属性名字。
- collection里不再进行第二次的select查询了,而是直接列名与属性名对应起来。mybatis会帮忙映射结果。
- 有一个新的 “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使用resultMap自定义映射规则与关联映射
③ difference-between-collection-and-association
④ 多对多查询