Mybatis之基本操作
不积跬步,无以至千里;不积小流,无以成江海。
今天我们看一下Mybatis里面的基本操作,一对多,多对一,多对多的使用。
1.一对多场景
学生和教室的关系。一个教室可以有多个学生,如何查询,老套路我们看代码.
第一步实体类
教室实体类
public class ClassRoom {
private int id ;
private String className;
private String classAddress;
private List<Student> studentList;//添加学生list属性
//get set省略
}
学生实体类
public class Student {
private int id;
private String name;
private boolean pass;
//get set 省略
}
第二步:接口
package com.jiepi.dao;
import java.util.List;
public interface ClassRoomMapper {
ClassRoom findOneToMany1(int id);//嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集
ClassRoom findOneToMany2(int id);//嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
}
第三步:Maper映射文件(有两种实现方式)
第一种实现方式嵌套结果映射
<resultMap id="oneToManyMap1" type="classroom">
<id column="cr_id" property="id" />
<result column="className" property="className"/>
<result column="classAddress" property="classAddress"/>
<collection property="studentList" column="cr_id" ofType="student">
<result column="name" property="name"/>
<result column="pass" property="pass"/>
</collection>
</resultMap>
<select id="findOneToMany1" resultMap="oneToManyMap1">
SELECT cr.id cr_id, className,classAddress ,name,pass from classroom cr inner join student std
on cr.id=std.classroomId where cr.id=#{id}
</select>
第二种:实现方式嵌套 Select 查询
<resultMap id="OneToManyMap2" type="classroom">
<id column="id" property="id" />
<result column="className" property="className"/>
<result column="classAddress" property="classAddress"/>
<collection property="studentList" column="id" ofType="student" select="findOneToManyChild2">
<result column="name" property="name"></result>
<result column="pass" property="pass"/>
</collection>
</resultMap>
<select id="findOneToMany2" resultMap="OneToManyMap2">
select id,className,classAddress from classroom where id =#{id}
</select>
<select id="findOneToManyChild2" resultType="student">
select * from student where classroomId=#{classroomId}
</select>
第三步:运行结果
@Test
public void find() {
ClassRoomMapper dao = session.getMapper(ClassRoomMapper.class);
ClassRoom classRoom = dao.findOneToMany1(1);
System.out.println(classRoom.toString());
}
@Test
public void find1() {
ClassRoomMapper dao = session.getMapper(ClassRoomMapper.class);
ClassRoom classRoom = dao.findOneToMany2(1);
System.out.println(classRoom.toString());
}
他们的运行结果是一样的
果然返回一个教室有多个学生
2.多对一场景
多个学生只有一个教室
学生实体类
public class Student {
private int id;
private String name;
private boolean pass;
private ClassRoom classRoom; //添加房间属性
//get set省略
}
教室实体类
public class ClassRoom {
private int id ;
private String className;
private String classAddress;
//get set 省略
}
第二步:接口
public interface StudentMapper {
List<Student> findManyToOne1();
List<Student> findManyToOne2();
}
第三步:配置映射文件
第一种实现方式
<resultMap id="ManyToOneMap1" type="student">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="pass" property="pass"/>
<association property="classRoom" javaType="classroom">
<result column="className" property="className"/>
<result column="classAddress" property="classAddress"/>
</association>
</resultMap>
<select id="findManyToOne1" resultMap="ManyToOneMap1">
SELECT name,pass,className,classAddress
FROM student stu INNER JOIN classroom cr
ON stu.classRoomId = cr.id
</select>
第二种实现方式
<resultMap id="ManyToOneMap2" type="student">
<result column="name" property="name"/>
<result column="pass" property="pass"/>
<association property="classRoom" javaType="classroom" column="classroomId" select="findManyToOne2Child">
<result column="className" property="className"/>
<result column="classAddress" property="classAddress"/>
</association>
</resultMap>
<select id="findManyToOne2" resultMap="ManyToOneMap2">
SELECT name,pass,classroomId FROM student
</select>
<select id="findManyToOne2Child" resultType="classroom">
SELECT id,className,classAddress FROM classroom WHERE id = #{id}
</select>
第四步:测试代码
@Test
public void selectList2() throws Exception {
StudentMapper dao = session.getMapper(StudentMapper.class);
List<Student> student = dao.findManyToOne2();
student.stream().forEach((it) -> {
System.out.println(it);
});
}
@Test
public void selectList2() throws Exception {
StudentMapper dao = session.getMapper(StudentMapper.class);
List<Student> student = dao.findManyToOne1();
student.stream().forEach((it) -> {
System.out.println(it);
});
}
果然返回多个学生在一个教室
3.多对多场景
一个学生有多个老师,一个老师有多个学生
教师实体类
public class Teacher {
private int id;
private String name;
private List<Student> studentList;//学生类表
}
学生实体类
public class Student {
private int id;
private String name;
private boolean pass;
private List<Teacher> teacherList;//教师列表
教师实体和学生实体关联实体类
public class TeacherAndStudent {
private Student student;//学生
private Teacher teacher;//教师
}
第二步:接口
public interface TeacherAndStudentMapper {
List<Teacher> findManyToMany();
}
第三步:映射文件
<resultMap id="ManyToMany" type="teacher">
<id column="teacherId" property="id"/>
<result column="teacherName" property="name"/>
<collection property="studentList" ofType="student" >
<id column="studentId" property="id"/>
<result column="studentName" property="name"/>
<result column="studentPass" property="pass"/>
</collection>
</resultMap>
<select id="findManyToMany" resultMap="ManyToMany">
SELECT t.id teacherId,t.name teacherName,s.id studentId,s.Name studentName,s.pass studentPass from teacherAndstudent tas INNER JOIN student s ON s.id=tas.studentId
INNER JOIN teacher t on t.id=tas.teacherId;
</select>
第四步:测试结果
@Test
public void find() {
TeacherAndStudentMapper dao = session.getMapper(TeacherAndStudentMapper.class);
List<Teacher> teacherList = dao.findManyToMany();
System.out.println(teacherList.toString());
}
果然返回多个老师,而每个老师有多个学生
我们把级联关系基本操作都已经演示完毕,为了让大家更加深刻,我们在再介绍一下基本的概念。
我们先解释下面的一段代码
<association property="classRoom" javaType="classroom" column="classroomId" select="findManyToOne2Child">
<result column="className" property="className"/>
<result column="classAddress" property="classAddress"/>
</association>
关联(association)元素处理“有一个”类型的关系。比如,在我们的示例中,一个学生有一个教室。关联结果映射和其它类型的映射工作方式差不多。你需要指定目标属性名以及属性的javaType(很多时候 MyBatis 可以自己推断出来),在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:
两种不同的方式我们已经演示,但是在我们使用嵌套 Select 查询会存在性能问题。虽然这种方式很简单,但在大型数据集或大型数据表上表现不佳。这个问题被称为“N+1 查询问题”。概括地讲,N+1 查询问题是这样子的:
这个问题会导致成百上千的 SQL 语句被执行。有时候,我们不希望产生这样的后果。
好消息是,MyBatis 能够对这样的查询进行延迟加载(fetchType,可选的。有效值为 lazy 和 eager),因此可以将大量语句同时运行的开销分散开来(就是先执行主表信息查出来sql语句,如果需要详情信息的时候,再去执行查询详情信息sql语句,分两步执行,有些时候我们只要主表信息,不需要详情信息,就不会执行子查询了)。然而,如果你加载记录列表之后立刻就遍历列表以获取嵌套的数据,就会触发所有的延迟加载查询,性能可能会变得很糟糕。
还有一点,我们必须注意一下,先看一段代码
<resultMap id="ManyToOneMap1" type="student">
<id column="id" property="id"/> //这个id元素很重要
<result column="name" property="name"/>
<result column="pass" property="pass"/>
<association property="classRoom" javaType="classroom">
<result column="className" property="className"/>
<result column="classAddress" property="classAddress"/>
</association>
</resultMap>
非常重要:id 元素在嵌套结果映射中扮演着非常重要的角色。你应该总是指定一个或多个可以唯一标识结果的属性。虽然,即使不指定这个属性,MyBatis 仍然可以工作,但是会产生严重的性能问题。只需要指定可以唯一标识结果的最少属性。显然,你可以选择主键(复合主键也可以)
集合元素(collection)和关联元素几乎是一样的,但是我们注意到一个新的 “ofType” 属性。
这个属性也是很重要的,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。所以你可以按照下面这样来阅读映射
<collection property="studentList" column="id" ofType="student" select="findOneToManyChild2">
studentList存储着student类型的ArrayList 集合。
官网提示对关联或集合的映射,并没有深度、广度或组合上的要求。但在映射时要留意性能问题。在探索最佳实践的过程中,应用的单元测试和性能测试会是你的好帮手.
Mybatis的讲解就到这里了,如果有机会我们会对Mybatis进一步讲解,下期我们会对spring进行讲解,欢迎持续关注,抽空还会对面试题进行讲解。有什么面试题不懂的,欢迎在公众号中留言,我会依次为大家总结解答。