目录
一、MyBatis关联查询
二、MyBatis一对一关联查询
三、MyBatis一对多关联查询
四、MyBatis多对多关联查询
五、MyBatis分解式查询_一对多
以查询班级时关联查询出学生为例,使用N+1(分解式)查询:
六、MyBatis分解式查询_一对一
七、MyBatis分解式查询_延迟加载
1.分解式查询的两种加载方式(立即加载、延迟加载):
2.开启延迟加载:
3.测试延迟加载
4.什么时候启用延迟加载:
一、MyBatis关联查询
介绍:
MyBatis 的关联查询分为一对一关联查询和一对多关联查询。
查询对象时,将关联的另一个 对象 查询出来,就是一对一关联查询。
查询对象时,将关联的另一个对象的 集合 查询出来,就是一对多关联查询。
例如有学生类和班级类:
一个学生对应一个班级,也就是学生类中有一个班级属性,这就是一对一关系。
一个班级对应多个学生,也就是班级类中有一个学生集合属性,这就是一对多关系。
1.实体类设计如下:
public class Student {
/**
* 主键也可以设置int类型,但是如果是用来接收form表单数据,
* 就得要设置为Integer,因为form表单传过来的值没有int类型,所以一般主键列设置为Integer类型
*/
private Integer sid;
private String name;
private int age;
private String sex;
// 一对一关联查询将结果封装到对象中
private Classes classes;
// 省略getter/setter/toString/构造器
}public class Classes {
/**
* 主键也可以设置int类型,但是如果是用来接收form表单数据,
* 就得要设置为Integer,因为form表单传过来的值没有int类型,所以一般主键列设置为Integer类型
*/
private Integer cid;
private String className;
// 一对多关联查询将结果封装到集合中
private List<Student> studentList;
// 省略getter/setter/toString/构造器
}
2.数据库设计如下:
二、MyBatis一对一关联查询
介绍:
查询学生时,将关联的一个班级对象查询出来,就是一对一关联查询(一个学生属于一个班级)。
1.创建持久层接口
public interface StudentMapper {
// 1.一对一关联查询
// 一对一关联查询是将结果封装到对象中
List<Student> findAll();
}
2.创建映射文件
<mapper namespace="com.itbaizhan.mapper.StudentMapper">
<!--通过resultMap映射字段名与实体类属性名的一一对应关系来解决-->
<!--id:自定义映射名(自己定义) type:自定义映射的对象类型-->
<resultMap id="studentMapper" type="com.itbaizhan.pojo.Student">
<!-- id:定义主键列 proprty:POJO属性名 column:数据库列名-->
<id property="sid" column="sid"></id>
<!-- result:定义普通列 property:POJO属性名 column:数据库列名-->
<result property="name" column="name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<!-- 一对一对象列 property:属性名 column:关联列名 javaType:关联对象类型-->
<association property="classes" column="classId" javaType="com.itbaizhan.pojo.Classes">
<!--id:定义关联对象主键列 proprty:POJO属性名 column:数据库列名-->
<id property="cid" column="cid"></id>
<!--result:定义关联对象普通列 property:POJO属性名 column:数据库列名-->
<result property="className" column="className"></result>
</association>
</resultMap>
<!-- 注意:返回结果是resultMap,不是resultType-->
<select id="findAll" resultMap="studentMapper">
select * from student left join classes on student.classId = classes.cid
</select>
</mapper>
3.在SqlMapConfig配置文件中注册映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itbaizhan.mapper.StudentMapper">
<!--通过resultMap映射字段名与实体类属性名的一一对应关系来解决-->
<!--id:自定义映射名(自己定义) type:自定义映射的对象类型-->
<resultMap id="studentMapper" type="com.itbaizhan.pojo.Student">
<!-- id:定义主键列 proprty:POJO属性名 column:数据库列名-->
<id property="sid" column="sid"></id>
<!-- result:定义普通列 property:POJO属性名 column:数据库列名-->
<result property="name" column="name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<!-- 一对一对象列(一对一关联查询用association,封装成对象) property:属性名 column:关联列名 javaType:关联对象类型-->
<association property="classes" column="classId" javaType="com.itbaizhan.pojo.Classes">
<!--id:定义关联对象主键列 proprty:POJO属性名 column:数据库列名-->
<id property="cid" column="cid"></id>
<!--result:定义关联对象普通列 property:POJO属性名 column:数据库列名-->
<result property="className" column="className"></result>
</association>
</resultMap>
<!-- 注意:返回结果是resultMap,不是resultType-->
<select id="findAll" resultMap="studentMapper">
select * from student left join classes on student.classId = classes.cid
</select>
</mapper>
4.测试一对一关联查询
public class TestManyTableQuery {
InputStream is = null;
SqlSession session = null;
@Before
public void before() throws IOException {
// (1)读取核心配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
// (2) 创建SqlSessionFactoryBuild对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// (3)SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
// (4)SqlSessionFactory对象获取SqlSession对象
session = factory.openSession();
session.getMapper(StudentMapper.class);
}
@After
public void after() throws IOException {
// 释放资源
session.close();
is.close();
}
// 一对一关联查询,实体类:Student
@Test
public void testFindAllStudent(){
StudentMapper studentMapper = session.getMapper(StudentMapper.class);
List<Student> all = studentMapper.findAll();
all.forEach(System.out::println);
}
}
三、MyBatis一对多关联查询
介绍:
查询班级时,将关联的学生集合查询出来,就是一对多关联查询(一个班级对应多个学生)。
1.创建持久层接口
public interface ClassesMapper {
// 2.一对多关联查询
// 一对多关联查询将结果封装到集合中
List<Classes> findAll();
}
2.创建映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itbaizhan.mapper.ClassesMapper">
<!--通过resultMap映射字段名与实体类属性名的一一对应关系来解决-->
<!--id:自定义映射名(自己定义) type:自定义映射的对象类型-->
<resultMap id="classesMapper" type="com.itbaizhan.pojo.Classes">
<!-- id:定义主键列 proprty:POJO属性名 column:数据库列名-->
<id property="cid" column="cid"></id>
<!-- result:定义普通列 property:POJO属性名 column:数据库列名-->
<result property="className" column="className"></result>
<!-- 一对多对象列(一对多关联查询用collection,封装成集合) property:属性名 column:关联列名 ofType:关联对象类型-->
<collection property="studentList" column="classId" ofType="com.itbaizhan.pojo.Student">
<!-- id:定义主键列 proprty:POJO属性名 column:数据库列名-->
<id property="sid" column="sid"></id>
<!-- result:定义普通列 property:POJO属性名 column:数据库列名-->
<result property="name" column="name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</collection>
</resultMap>
<!-- 注意:返回结果是resultMap,不是resultType-->
<select id="findAll" resultMap="classesMapper">
select * from classes left join student on classes.cid = student.classId;
</select>
</mapper>
3.测试一对多关联查询
// 一对多关联查询,实体类:Classes
@Test
public void testFindAllClasses(){
ClassesMapper classesMapper = session.getMapper(ClassesMapper.class);
List<Classes> all = classesMapper.findAll();
all.forEach(System.out::println);
}
四、MyBatis多对多关联查询
介绍:
MyBatis 多对多关联查询本质就是两个一对多关联查询。
例如有老师类和班级类:
一个老师对应多个班级,也就是老师类中有一个班级集合属性。
一个班级对应多个老师,也就是班级类中有一个老师集合属性。
1.实体类设计如下:
public class Classes {
/**
* 主键也可以设置int类型,但是如果是用来接收form表单数据,
* 就得要设置为Integer,因为form表单传过来的值没有int类型,所以一般主键列设置为Integer类型
*/
private Integer cid;
private String className;
// 一对多关联查询将结果封装到集合中
private List<Student> studentList;
private List<Teacher> teacherList;
// 省略getter/setter/toString/构造器
}public class Teacher {
/**
* 主键也可以设置int类型,但是如果是用来接收form表单数据,
* 就得要设置为Integer,因为form表单传过来的值没有int类型,所以一般主键列设置为Integer类型
*/
private Integer tid;
private String tname;
private List<Classes> classesList;
// 省略getter/setter/toString/构造器
}
数据库设计:
在数据库设计中,需要建立中间表,双方与中间表均为一对多关系。
3.创建持久层接口
public interface TeacherMapper {
// 多对多关联查询:相当于两个一对多关联查询
List<Teacher> findAll();
}
4.创建映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itbaizhan.mapper.TeacherMapper">
<!--通过resultMap映射字段名与实体类属性名的一一对应关系来解决-->
<!--id:自定义映射名(自己定义) type:自定义映射的对象类型-->
<resultMap id="teacherMapper" type="com.itbaizhan.pojo.Teacher">
<!-- id:定义主键列 proprty:POJO属性名 column:数据库列名-->
<id property="tid" column="tid"></id>
<!-- result:定义普通列 property:POJO属性名 column:数据库列名-->
<result property="tname" column="tname"></result>
<!-- 多对多对象列(多对多关联查询用collection,封装成集合) property:属性名 column:关联列主键名 ofType:关联对象类型-->
<collection property="classesList" column="cid" ofType="com.itbaizhan.pojo.Classes">
<!-- id:定义主键列 proprty:POJO属性名 column:数据库列名-->
<id property="cid" column="cid"></id>
<!-- result:定义普通列 property:POJO属性名 column:数据库列名-->
<result property="className" column="className"></result>
</collection>
</resultMap>
<!-- 注意:返回结果是resultMap,不是resultType-->
<select id="findAll" resultMap="teacherMapper">
select * from teacher
left join classes_teacher
on teacher.tid = classes_teacher.tid
left join classes
on classes_teacher.cid = classes.cid;
</select>
</mapper>
5.测试多对多关联查询
// 多对多关联查询,实体类:Teacher
@Test
public void testFindAllTeacher(){
TeacherMapper teacherMapper = session.getMapper(TeacherMapper.class);
List<Teacher> all = teacherMapper.findAll();
all.forEach(System.out::println);
}
五、MyBatis分解式查询_一对多
1.介绍:
在MyBatis多表查询中,使用连接查询时一个Sql语句就可以查询出所有的数据。如:
//查询班级时关联查询出学生
select * from classes left join student on classes.cid = student.classId;
也可以使用分解式查询,即将一个连接
Sql
语句分解为多条
Sql
语句,也叫N+1查询;如:
//查询班级时关联查询出学生
select *from classes;
select *from student where classId = 1;
select *from student where classId = 2;
2.连接查询、分解式查询优缺点
连接查询:
优点:降低查询次数,从而提高查询效率。
缺点:如果查询返回的结果集较多会消耗内存空间。
N+1 查询:
优点:结果集分步获取,节省内存空间。
缺点:由于需要执行多次查询,相比连接查询效率低。
以查询班级时关联查询出学生为例,使用N+1(分解式)查询:
1.创建每个查询语句的持久层接口方法
public interface ClassesMapper2 {
// 分解式查询_一对多
// 查询所有班级并关联查出班级对应的学生
List<Classes> findAll();
}public interface StudentMapper2 {
// 根据班级Id查询所有学生
List<Student> findByClassId(int classId);
}
2.在映射文件中进行配置
//ClassesMapper2.xml
<select id="findAll" resultType="com.itbaizhan.pojo.Classes">
select * from classes
</select>//StudentMapper2.xml
<select id="findByClassId" resultType="com.itbaizhan.pojo.Student" parameterType="int">
select * from student where classId = ${classId}
</select>
3.修改主表映射文件中的查询方法
N+1查询与连接查询相比,需要在 <collection> 或 <association> 中添加属性<select>
//ClassesMapper2.xml
<mapper namespace="com.itbaizhan.mapper2.ClassesMapper2">
<!--1.分解式查询_一对多
先查询出所有班级,然后再查询出每个班级对应的学生,所以用<resultMap>映射一一对应关系-->
<!--自定义映射关系-->
<resultMap id="MyClassesMapper" type="com.itbaizhan.pojo.Classes">
<id property="cid" column="cid"></id>
<result property="className" column="className"></result>
<!--property:属性名 ofType:属性的泛型 select:从表查询调用的方法 column:调用方法时传入的参数字段-->
<!--注意:1.分解式查询要用select调用从表(Student表)中的方法才能引入
2.要用column引入从表中的主键,因为通过从表主键来查询到从表的数据-->
<collection property="studentList"
ofType="com.itbaizhan.pojo.Student"
select="com.itbaizhan.mapper2.StudentMapper2.findByClassId"
column="cid">
</collection>
</resultMap>
</mapper>
4.测试查询方法
// 分解式查询_一对多
@Test
public void testFindAllClasses2(){
ClassesMapper2 classesMapper2 = session.getMapper(ClassesMapper2.class);
List<Classes> all = classesMapper2.findAll();
all.forEach(System.out::println);
}
5.查询结果
我们可以看到在控制台打印出了多条Sql语句,表示执行了多条sql语句;
六、MyBatis分解式查询_一对一
介绍:
查询学生时关联查询出班级也可以使用分解式查询,首先将查询语句分开:
select * from student;
select * from classes where cid = ?
1.创建每个查询语句的持久层方法
//接口ClassesMapper2
// 根据Id查询班级
Classes findByCid(int cid);//接口StudentMapper2
// 分解式查询_一对一
// 查询所有学生并关联查出学生对应的班级
List<Student> findAll();
2.在映射文件中进行配置
//ClassesMapper2.xml
<!--分解式查询_一对一-->
<select id="findByCid" resultType="com.itbaizhan.pojo.Classes" parameterType="int">
select * from classes where cid = ${cid}
</select>//StudentMapper2.xml
<select id="findAll" resultType="com.itbaizhan.pojo.Studnet">
select * from student
</select>
3.修改主表映射文件中的查询方法
N+1查询与连接查询相比,需要在 <collection> 或 <association> 中添加属性<select>
//StudentMapper2.xml
<!--分解式查询_一对一-->
<resultMap id="MyStudentMapper" type="com.itbaizhan.pojo.Student">
<id property="sid" column="sid"></id>
<result property="name" column="name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<association property="classes"
javaType="com.itbaizhan.pojo.Classes"
select="com.itbaizhan.mapper2.ClassesMapper2.findByCid"
column="classId">
</association>
</resultMap>
<select id="findAll" resultMap="MyStudentMapper">
select * from student
</select>
4.测试查询方法
// 分解式查询_一对一
@Test
public void testFindAllStudent2(){
StudentMapper2 studentMapper2 = session.getMapper(StudentMapper2.class);
List<Student> all = studentMapper2.findAll();
all.forEach(System.out::println);
}
5.查询结果
七、MyBatis分解式查询_延迟加载
1.分解式查询的两种加载方式(立即加载、延迟加载):
立即加载:在查询主表时就执行所有的 Sql 语句。
延迟加载:又叫懒加载,首先执行主表的查询语句,使用从表数据时才触发从表的查询语句。 延迟加载在获取关联数据时速度较慢,但可以节约资源,即用即取。
2.开启延迟加载:
(1)在SqlMapConfig.xml配置文件中开启:
设置所有的N+1 查询都为延迟加载:
<settings>
<!--此方式为:设置所有的分解式(N+1)查询都为延迟加载:
注意:这种方式很少使用,因为不可能为所以分解式查询都设置为延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
(2)设置某个方法为延迟加载:
在
<association> 或
<collection>
中添加
fetchType
属性设置加载方式。
lazy
:延迟加载;
eager
:立即加载。
<!--为一对多分解式查询加入延迟加载
fetchType:加载类型 lazy:懒(延迟)加载 eager:立即加载-->
<resultMap id="MyClassesMapper" type="com.itbaizhan.pojo.Classes">
<id property="cid" column="cid"></id>
<result property="className" column="className"></result>
<collection property="studentList"
ofType="com.itbaizhan.pojo.Student"
select="com.itbaizhan.mapper2.StudentMapper2.findByClassId"
column="cid"
fetchType="lazy">
</collection>
</resultMap>
<select id="findAll" resultMap="MyClassesMapper">
select * from classes
</select>
3.测试延迟加载
(1)测试延迟加载
// 分解式查询_一对多:测试延迟加载
@Test
public void testFindAllClasses3(){
ClassesMapper2 classesMapper2 = session.getMapper(ClassesMapper2.class);
List<Classes> all = classesMapper2.findAll();
all.forEach(System.out::println);
System.out.println("------------------------------");
System.out.println(all.get(0).getStudentList());
}
(2)测试结果
由于打印对象时会调用对象的 toString 方法, toString 方法默认会触发延迟加载的查询,所以我们无法测试出延迟加载的效果。
(3)在配置文件中设置lazyLoadTriggerMethods属性
我们在配置文件中设置lazyLoadTriggerMethods属性,该属性可以指定对象的什么方法触发延迟加载,设置为空字符串即可
<!--此方式为:映射文件中<collection>或<association>中设置了fetchType的方法触发延迟加载,设置为空字符串即可。-->
<settings>
<setting name="lazyLoadTriggerMethods" value=""/>
</settings>
(4)再次测试延迟加载并显示结果:
4.什么时候启用延迟加载:
一般情况下,一对多查询使用延迟加载,一对一查询使用立即加载。因为一对多可能有上万甚至更多的数据,如果立即加载的话会很占用空间,所以使用延迟加载;而一对一查询的话,只有一条数据,立即加载也不会有什么影响,所以可以立即加载。
5.知识点整理:
(1)在配置文件的 <setting>中, name 值配置成“lazyLoadingEnabled” ,这种情况下所有的N+1(分解式)查询都是懒加载。
(2)在 <association> 、 <collection> 中添加<fetchType>可以设置加载方式,
lazy:懒加载 eager:立即加载。