文章目录
前言
笔者在学习MyBatis课程中,听了很多慕课,最终发现还是狂神老师讲的最好。本篇是根据狂神老师的 狂神说MyBatis05:一对多和多对一处理
学习完后的整理笔记,结合了狂神老师的讲解和自己的理解,旨在弄通这个知识点。
如果本文中的一些基本概念不了解,比如resultMap,collection,和association这些字段看不懂,建议先去科普一下MyBatis的基本配置:
简单讲解MyBatis中的resultMap,collection,association
一、多对一关系的查询
数据表:
实体类:
其中 学生和教师是多对一关系, 所以在学生表中有一个外键,指向教师表的主键
虽然在数据表中,外键是int型的(tid), 只负责指向教师表的主键id. 但是在实体类中没有这个int型的外键(tid), 取而代之的直接是一个教师类型的实体类.
这里很蒙是不是? 原因就在于, 从需求角度考虑, 我们想要查出来的结果是下图这个东西:
现在是不是明白多了? 实体类不一定要完全和数据库中的字段相对应,它是要为展示数据负责的(最终你想要查出来什么结果,实体类就是什么样子的)
对应的mapper文件:
方法一: 嵌套select查询(按照查询进行嵌套处理)
<!--
需求:获取所有学生及对应老师的信息
思路:
1. 获取所有学生的信息
2. 根据获取的学生信息的老师ID->获取该老师的信息
3. 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般使用关联查询?
1. 做一个结果集映射:StudentTeacher
2. StudentTeacher结果集的类型为 Student
3. 学生中老师的属性为teacher,对应数据库中为tid。
多个 [1,...)学生关联一个老师=> 一对一,一对多
4. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查询
-->
<select id="getStudents" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<!--
这里传递过来的id,只有一个属性的时候,下面可以写任何值
association中column多参数配置:
column="{key=value,key=value}"
其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。
-->
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{tid}
</select>
观察该方法,为什么叫嵌套select? 因为你看下面这一行有一个select字段:
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
该select字段直接对应了最下面那个id为getTeacher的标签. 这其实是嵌套了另一个标签的操作. 注意看该标签里面的sql语句:
select * from teacher where id = #{tid}
id = #{tid} , 这个tid就是前面association标签里面 column=“tid” 里传过来的tid。
方法二: 按照结果进行嵌套处理
<!--
按查询结果嵌套处理
思路:
1. 直接查询出结果,进行结果集的映射
-->
<select id="getStudents2" resultMap="StudentTeacher2" >
select s.id sid, s.name sname , t.name tname
from student s,teacher t
where s.tid = t.id
</select>
<resultMap id="StudentTeacher2" type="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<!--关联对象property 关联对象在Student实体类中的属性-->
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
先讲一下sql语句:
select s.id sid, s.name sname , t.name tname
from student s,teacher t
where s.tid = t.id
s是student的别名(之前有配),看不懂的话可以直接写student。t就是teacher,看不懂的话换成teacher也行,不用纠结这里,反正s和t就是代表学生表和老师表的意思
s.id后面跟的sid, 其实是在sql里起了一个别名. 后面的sname和tname也是sql语法中的别名
所以, 后面配置resultMap时的column属性, 本来对应的虽说是数据库字段, 但是由于数据库字段都有别名了, 也应该配置别名的形式.
看下面的association代码:
<!--关联对象property 关联对象在Student实体类中的属性-->
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
这里是不是看不懂? 别急, 看下面最终查询结果是个什么东西, 你就知道结果集映射是配的啥了
上图就是查询的结果, 其实就是Student的pojo实体类中的三个字段。
这次再来看前面的结果集映射代码,id和name字段都配好了,剩下的就是teacher字段。
按理说这里需要配置teacher的id和name。但是上面的代码中只配置了name属性,
因为我们的需求根本不需要查teacher的id属性,只需要查teacher->name(select s.id sid, s.name sname , t.name tname)。括号里也就是我们的sql语句,从语句中看需求只需要查出来 student的id属性, student的name属性 和 teacher的name属性这三个即可。
这也解释了上图为啥id全都是0(本来秦老师的id是1),因为我们根本就没配id属性。
二、一对多关系的查询
问题来了,前面已经说了多对一 ,那这里为啥要再重新学一遍一对多呢?
看下图,下图是多对一查出来的结果,学生是多的一方,最后查出来的是学生的信息:
但是如果我们需求是:以老师为主体,查老师的信息呢?
想实现上面的需求,首先需要改动实体类:
最终查出来的信息是长这个样的:
(注意,这里的students是一个List,泛型是<Student>
)
废话不多说,下面开始介绍如何查询:
其实和前面的多对一查询的两种方式大同小异,都是按照结果进行嵌套处理 或者 按照查询进行嵌套处理
方式一:按照结果进行嵌套处理
- TeacherMapper接口编写方法
//获取指定老师,及老师下的所有学生
public Teacher getTeacher(int id);
- 编写接口对应的Mapper配置文件
<mapper namespace="com.kuang.mapper.TeacherMapper">
<!--
思路:
1. 从学生表和老师表中查出学生id,学生姓名,老师姓名
2. 对查询出来的操作做结果集映射
1. 集合的话,使用collection!
JavaType和ofType都是用来指定对象类型的
JavaType是用来指定pojo中属性的类型
ofType指定的是映射到list集合属性中pojo的类型。
-->
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid, s.name sname , t.name tname, t.id tid
from student s,teacher t
where s.tid = t.id and t.id=#{id}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid" />
<result property="name" column="sname" />
<result property="tid" column="tid" />
</collection>
</resultMap>
</mapper>
解释一下,sql语句的最后一行 where s.tid = t.id and t.id=#{id}
里面的#{id},就是接口传进来的参数public Teacher getTeacher(int id);
方式二:按照查询进行嵌套处理
- TeacherMapper接口编写方法
//获取指定老师,及老师下的所有学生
public Teacher getTeacher2(int id);
- 编写接口对应的Mapper配置文件
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from teacher where id = #{id}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<!--column是一对多的外键 , 写的是一的主键的列名-->
<collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
select * from student where tid = #{id}
</select>
注意看这里的javaType字段,写的是ArrayList!因为传进去的返回值类型的确是List类型,而Student类只是List的泛型,泛型要用ofType来传递。
不要小看这里的细节,出错的往往是这些细节!
查询结果
不管是用上面的哪种方法,结果都是长得像下图这个样的,即获取指定老师下的所有学生及老师的信息:
总结
这里介绍了两种方法查询:
按照查询进行嵌套处理 和 按照结果进行嵌套处理
按照查询进行嵌套处理就像SQL中的子查询
按照结果进行嵌套处理就像SQL中的联表查询
按照结果进行嵌套处理往往要写复杂的SQL语句,按照查询进行嵌套处理往往要进行复杂的映射配置。
笔者比较喜欢按照结果进行嵌套处理,因为配置resultMap的映射本质就是为了解决属性名和字段不一致的问题,把sql语句写完整了,可以得出多种映射的结果,只需要照着配置就完了。况且掌握复杂查询的SQL语句本身就是一个程序猿的基本功。
最后还差一个多对多关系的处理的总结,日后再更新。
这个知识点如果学不会,完全就是SQL基础没打好的问题 ,建议重新学一下数据库基础。