10、多对一情景
情景:一个教师负责教多个学生。
-
站在学生的角度,学生对教师是多对一的关联关系;
-
站在教师的角度,教师对学生是一对多的集合关系。
需求:查询所有学生,及相关联的教师信息。
10.1、环境搭建
1、数据库表
teacher表
student表
2、实体类
根据需求,学生表中的教师设为实体类,方便引用。
以下只列出实体类属性,自行添加 getter和 setter、构造方法等。
Teacher类
/**
* 教师ID
*/
private int id;
/**
* 教师姓名
*/
private String name;
public Teacher() {
}
Student类
/**
* 学生ID
*/
private int id;
/**
* 学生姓名
*/
private String name;
/**
* 关联教师
*/
private Teacher teacher;
3、Mapper
先看看直接查询出来是什么结果,再分析该如何处理。
StudentMapper
/**
* 查询所有学生,以及关联教师信息
* @return 学生列表
*/
List<Student> listStudents();
StudentMapper.xml
<select id="listStudents" resultType="student">
select *
from mybatis.student;
</select>
注:
-
Mapper.xml
建议存放在resources目录下,与接口同级。 - 需要在
MyBatis配置文件
中注册Mapper
或Mapper.xml
!
4、JUnit
@Test
public void testListStudents() {
// 获取SqlSession实例
SqlSession sqlSession = MyBatisUtils.getSqlSession();
// 获取Mapper
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// 执行方法
List<Student> students = mapper.listStudents();
for (Student student : students) {
System.out.println(student);
}
// 关闭连接
sqlSession.close();
}
查询结果:teacher为null
原因:
- 数据库中的
student
表,字段为tid
:关联教师的ID; - 实体类中的
Student
类,属性为teacher
:关联教师的对象引用(实现逻辑外键); - 它们之间无法自动映射,所以需要通过Mapper配置结果集映射;
- 在学生的角度,学生与教师是
多对一
的关系,即关联(association)
关系。
10.2、多对一处理
站在学生的角度,学生对教师是多对一的关联关系。
有以下两种方法:
1、子查询
StudentMapper.xml
<select id="listStudents" resultMap="studentMap">
select id, name, tid
from mybatis.student;
</select>
<resultMap id="studentMap" type="student">
<!-- 数据库字段和实体类属性能自动映射,可以删去 -->
<result property="id" column="id"/>
<result property="name" column="name"/>
<!-- 学生对教师:一对多,是关联关系 -->
<association property="teacher" column="tid" javaType="teacher" select="getTeacherById"/>
</resultMap>
<select id="getTeacherById" resultType="teacher">
select id, name
from mybatis.teacher
where id = #{tid};
</select>
分析:
-
原本的
select标签
中的resultType
已无法满足需求,所以应该用resultMap
结果映射。 -
创建一个
resultMap
,用于处理数据库和实体类的映射关系:-
result
标签:处理简单映射,若数据库字段和实体类属性能自动映射,无需显式配置;-
property
:实体类的属性名; -
column
:数据库表的字段名。
-
-
association
标签:处理复杂映射,代表一对多的关联关系;-
property
:实体类的属性名; -
column
:数据库表的字段名; -
javaType
:属性的Java类型,使用全限类名或别名; -
select
:引用子查询语句,Teacher需要通过学生表的tid查出;
-
-
-
相当于以下SQL语句:
select s.id as s_id, s.name as s_name, s.tid as t_id, (select name from teacher where id = t_id)as t_name from mybatis.student as s;
2、联表查询
StudenMapper.xml
<select id="listStudents" resultMap="studentMap">
select s.id as s_id,
s.name as s_name,
s.tid as t_id,
t.name as t_name
from mybatis.student as s
left join mybatis.teacher as t
on s.tid = t.id
</select>
<resultMap id="studentMap1" type="student">
<result property="id" column="s_id"/>
<result property="name" column="s_name"/>
<association property="teacher" javaType="teacher">
<result property="id" column="t_id"/>
<result property="name" column="t_name"/>
</association>
</resultMap>
分析:
-
原本的
select标签
中的resultType
已无法满足需求,所以应该用resultMap
结果映射; -
创建一个
resultMap
,用于处理数据库和实体类的映射关系:- 使用联表查询,select子句后的字段可能出现同名(如两张表中都有name字段)。因此需要为每个字段起别名,并在
resultMap
中配置。 -
result
标签:处理简单映射,由于SQL语句中起了别名,所以需要显式配置;-
property
:实体类的属性名; -
column
:数据库表的字段名。
-
-
association
标签:处理复杂映射,代表一对多的关联关系;-
property
:实体类的属性名; -
javaType
:属性的Java类型,使用全限类名或别名; - 相比子查询:无需配置
column
和select
两个属性,但需要在内部配置result标签
,将查到的teacher的字段
映射到Teacher类的属性
上。
-
- 使用联表查询,select子句后的字段可能出现同名(如两张表中都有name字段)。因此需要为每个字段起别名,并在
-
相当于以下SQL语句:
select s.id as s_id, s.name as s_name, s.tid as t_id, t.name as t_name from mybatis.student as s left join mybatis.teacher as t on s.tid = t.id;
3、对比
建议:使用联表查询!
子查询
- 查出
student
表包含的属性:属于Student本身的属性
(id、name),跟Teacher有关的属性
(逻辑外键tid); - 使用
resultMap
映射:tid
和teacher
; - 通过子查询,将
teacher表
的属性查出并封装到Teacher对象
中。
联表查询
- 查出所有属性:
属于Student本身的属性
(id、name),跟Teacher有关的属性
(逻辑外键tid),并通过联表查询查出属于Teacher本身的属性
(name); - 为字段起别名,方便
resultMap
结果映射; - 使用
resultMap
结果集映射:所有字段和属性。
11、一对多情景
需求:查询一个教师,及包含的学生集合。
11.1、环境搭建
1、数据库表
无需变动,同上。
2、实体类
根据需求:
-
需要查询一个教师及其包含学生的集合,所以为 Teacher类增加一个学生集合的属性。
-
不再那么关注
Student
类的属性中的Teacher引用
,所以修改为tid
即可。
Teacher类
/**
* 教师ID
*/
private int id;
/**
* 教师姓名
*/
private String name;
/**
* 学生集合
*/
private List<Student> studentList;
Student类
/**
* 学生ID
*/
private int id;
/**
* 学生姓名
*/
private String name;
/**
* 关联教师ID
*/
private int tid;
3、Mapper
先看看直接查询出来是什么结果,再分析该如何处理。
TeacherMapper
/**
* 通过ID查询教师
* @param id 教师ID
* @return 教师
*/
Teacher getTeacherById(int id);
TeacherMapper.xml
<select id="getTeacherById" resultType="teacher">
select * from mybatis.teacher where id = #{id}
</select>
4、JUnit
@Test
public void testGetTeacherById(){
// 获取SqlSession实例
SqlSession sqlSession = MyBatisUtils.getSqlSession();
// 获取Mapper
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
// 执行方法
Teacher teacher = mapper.getTeacherById(1);
System.out.println(teacher);
// 关闭连接
sqlSession.close();
}
查询结果:学生列表为空。
原因:
- 数据库
teacher表
中没有关于student的字段,无法映射,所以需要通过Mapper配置结果集映射; - 在教师的角度,教师与学生是
一对多
的关系,即集合(collection)关系。
11.2、一对多处理
站在教师的角度,教师对学生是一对多的集合关系。
有以下两种方法:
1、子查询
TeacherMapper.xml
<select id="getTeacherById1" resultMap="teacherMap1">
select id, name
from mybatis.teacher
where id = #{id}
</select>
<resultMap id="teacherMap1" type="teacher">
<!-- column代表传递给子查询的值 -->
<collection property="studentList" column="id" javaType="List" ofType="student" select="listStudentsByTid"/>
</resultMap>
<select id="listStudentsByTid" resultType="student">
select *
from mybatis.student
where tid = #{id}
</select>
分析:
-
原本的
select标签
中的resultType
已无法满足需求,所以应该用resultMap
结果映射。 -
创建一个
resultMap
,用于处理数据库和实体类的映射关系:-
result
标签:处理简单映射,若数据库字段和实体类属性能自动映射,无需显式配置; -
collection
标签:处理复杂映射,代表多对一的关联关系;-
property
:实体类的属性名; -
column
:传递给子查询的参数思路:这里本应该填写数据库表中的有关学生的字段,但是teacher表中没有相应字段,所以先空着。写到后面发现,没有地方给子查询语句传参,所以此处填写的值就是给子查询语句传递参数的。
-
javaType
:属性的Java类型,使用全限类名或别名; -
select
:引用子查询语句,Teacher需要通过学生表的tid查出;
-
-
2、联表查询
TeacherMapper
<select id="getTeacherById" resultMap="teacherMap">
select t.id as t_id,
t.name as t_name,
s.id as s_id,
s.name as s_name
from mybatis.teacher as t
left join mybatis.student s
on t.id = s.tid
where t.id = #{id}
</select>
<resultMap id="teacherMap" type="teacher">
<result property="id" column="t_id"/>
<result property="name" column="t_name"/>
<collection property="studentList" javaType="ArrayList" ofType="student">
<result property="id" column="s_id"/>
<result property="name" column="s_name"/>
<result property="tid" column="t_id"/>
</collection>
</resultMap>
分析:
-
原本的
select标签
中的resultType
已无法满足需求,所以应该用resultMap
结果映射; -
创建一个
resultMap
,用于处理数据库和实体类的映射关系:- 使用联表查询,select子句后的字段可能出现同名(如两张表中都有name字段)。因此需要为每个字段起别名,并在
resultMap
中配置。 -
result
标签:处理简单映射,由于SQL语句中起了别名,所以需要显式配置;-
property
:实体类的属性名; -
column
:数据库表的字段名。
-
-
collection
标签:处理复杂映射,代表多对一的关联关系;-
property
:实体类的属性名; -
javaType
:属性的Java类型,使用全限类名或别名; -
ofType
:映射到List集合中指定的泛型类型,使用全限类名或别名; - 相比子查询:无需配置
column
和select
两个属性,但需要在内部配置result标签
,将查到的student的字段
映射到Student类的属性
上。
-
- 使用联表查询,select子句后的字段可能出现同名(如两张表中都有name字段)。因此需要为每个字段起别名,并在
-
相当于以下SQL语句
select t.id as t_id, t.name as t_name, s.id as s_id, s.name as s_name from teacher as t left join student s on t.id = s.tid where t.id = ?
3、对比
建议:使用联表查询!
子查询:
- 查出student表包含的属性:
属于Student本身的属性
(id、name),跟Teacher有关的属性
(逻辑外键tid); - 使用resultMap映射:
tid
和teacher
; - 通过子查询,将teacher的属性查出并封装到Teacher对象中。
联表查询:
- 查出所有属性:
属于Teacher本身的属性
(id、name),并通过联表查询查出Student的属性
(name); - 为字段起别名,方便resultMap结果映射;
- 使用resultMap结果集映射:所有字段和属性。