一、学习目标
在之前的博客里,学习的数据库模型都是单一的,还是那句话,如果世界总是那么美好就好了!在数据库设计和关系型数据建模中,多对一(Many-to-One)和一对多(One-to-Many)是两种非常基础且常见的实体间关系,它们是数据库设计中非常基础且强大的概念,它们帮助我们在逻辑上组织和表示现实世界中的复杂关系。
二、多对一
1.搭建测试环境
1.数据库
USE mybatis;
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO teacher(`id`, `name`) VALUES (1, 'Mr.柳');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
2.实体类
@Data
public class Student {
private int id;
private String name;
//多个学生可以是同一个老师,即多对一
private Teacher teacher;
}
@Data
public class Teacher {
private int id;
private String name;
//一个老师多个学生
private List<Student> students;
}
3.mapper接口
public interface StudentMapper {
//List<Student> selectStudent();
//获取所有学生及对应老师的信息
public List<Student> getStudents();
}
public interface TeacherMapper {
//获取指定老师,及老师下的所有学生
public Teacher getTeacher(int id);
}
4.嵌套 Select 查询
在mybatis官方文件中提到,关联结果映射和其它类型的映射工作方式差不多。 你需要指定目标属性名以及属性的javaType
(很多时候 MyBatis 可以自己推断出来),在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。
关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:
1.嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
2.嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。
嵌套 Select 查询,
<select id="getStudents" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<association property="teacher" column="{id=tid,name=tid}" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{id}
</select>
就是这么简单。我们有两个 select 查询语句:一个用来加载学生信息(student),另外一个用来加载老师(teacher),而且学生的结果映射描述了应该使用 getTeacher 语句加载它的 teacher属性。其它所有的属性将会被自动加载,只要它们的列名和属性名相匹配。
然而,在mybatis官方文档中提到,这种方式虽然很简单,但在大型数据集或大型数据表上表现不佳。这个问题被称为“N+1 查询问题”。 概括地讲,N+1 查询问题是这样子的:
- 你执行了一个单独的 SQL 语句来获取结果的一个列表(就是“+1”)。
- 对列表返回的每条记录,你执行一个 select 查询语句来为每条记录加载详细信息(就是“N”)。
这个问题会导致成百上千的 SQL 语句被执行。有时候,我们不希望产生这样的后果。
好消息是,MyBatis 能够对这样的查询进行延迟加载,因此可以将大量语句同时运行的开销分散开来。 然而,如果你加载记录列表之后立刻就遍历列表以获取嵌套的数据,就会触发所有的延迟加载查询,性能可能会变得很糟糕。
所以,我们需要学习另一种方法,也就是上面提到的:关联的嵌套结果映射。我将在之后一对多的学习中使用方法jieguo。
5.导入映射
<mappers>
<!--引入映射文件-->
<mapper resource="Mapper/StudentMapper.xml"/>
<!--mapper resource="mapper/TeacherMapper.xml"-->
</mappers>
6.测试
@Test
public void testGetStudents(){
SqlSession session = MybatisUtils.getSession();
StudentMapper mapper = session.getMapper(StudentMapper.class);
List<Student> students = mapper.getStudents();
for (Student student : students){
System.out.println(student);
}
session.close();
}
结果:
三、一对多
1.搭建测试环境
1.实体类
@Data
public class Student {
private int id;
private String name;
private int tid;
}
@Data
public class Teacher {
private int id;
private String name;
//一个老师多个学生
private List<Student> students;
}
2.嵌套结果映射
<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>
现在我们将学生表和老师表连接在一起,而不是执行一个独立的查询语句,注意查询中的连接,以及为确保结果能够拥有唯一且清晰的名字,我们设置的别名。 这使得进行映射非常简单。
3.导入映射
<mappers>
<!--引入映射文件-->
<mapper resource="Mapper/StudentMapper.xml"/>
<mapper resource="mapper/TeacherMapper.xml"/>
</mappers>
4.测试
@Test
public void testGetTeacher(){
SqlSession session = MybatisUtils.getSession();
TeacherMapper mapper = session.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher.getName());
System.out.println(teacher.getStudents());
}
结果: