目录
一、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/构造器 }
2.数据库设计:
在数据库设计中,需要建立中间表,双方与中间表均为一对多关系。
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:立即加载。