MyBatis:复杂结果集映射与分步查询
前言什么是 MyBatis 结果映射MyBatis 作为半自动化的 ORM 框架结果映射ResultMap是其最核心、最强大的功能。它的作用是自动将数据库查询返回的结果集字段映射到 Java 实体类属性解决数据库列名与实体属性不一致、多表关联数据封装、复杂对象嵌套等问题。在多表关联查询场景中普通的结果映射无法处理关联对象一对一、多对一和集合一对多因此需要使用association和collection标签实现复杂映射并可配合分步查询 延迟加载进一步提升性能。一、数据表结构与测试数据1. 学生表SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0; DROP TABLE IF EXISTS student; CREATE TABLE student ( id int(11) NOT NULL AUTO_INCREMENT, Sname varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, sex varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, age int(11) DEFAULT NULL, t_id int(11) DEFAULT NULL, PRIMARY KEY (id) USING BTREE ) ENGINE InnoDB AUTO_INCREMENT 11 CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Compact; INSERT INTO student VALUES (1, 张三, 男, 18, 1); INSERT INTO student VALUES (2, 李四, 女, 18, 1); INSERT INTO student VALUES (3, 王五, 男, 18, 1); INSERT INTO student VALUES (4, 小白, 女, 18, 1); INSERT INTO student VALUES (5, 小黑, 男, 18, 1); INSERT INTO student VALUES (6, 小红, 女, 20, 2); INSERT INTO student VALUES (7, 小李, 男, 20, 2); INSERT INTO student VALUES (8, 小张, 女, 20, 2); INSERT INTO student VALUES (9, 小赵, 男, 20, 2); INSERT INTO student VALUES (10, 小王, 女, 20, 2); SET FOREIGN_KEY_CHECKS 1;代码解释创建学生表包含学生 id、姓名、性别、年龄、关联教师 id插入 10 条测试数据学生通过t_id关联老师。2. 老师表SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0; DROP TABLE IF EXISTS teacher; CREATE TABLE teacher ( id int(11) NOT NULL AUTO_INCREMENT, Tname varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, PRIMARY KEY (id) USING BTREE ) ENGINE InnoDB AUTO_INCREMENT 3 CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Compact; INSERT INTO teacher VALUES (1, 张老师); INSERT INTO teacher VALUES (2, 李老师); SET FOREIGN_KEY_CHECKS 1;代码解释创建教师表包含教师 id、姓名插入 2 条测试数据作为学生表的关联表。二、一对一、多对一映射业务场景查询学生信息同时关联查询所属授课老师多个学生对应一个老师方式一嵌套结果联表查询实体类定义public class Student { private Integer id; private String Sname; private String sex; private Integer age; private Integer t_id; private Teacher teacher; // 关联老师对象实现多对一映射 } public class Teacher { private Integer id; private String Tname; }代码解释Student 类中添加 Teacher 对象用于接收关联查询的教师数据Teacher 类为普通实体类。Mapper 映射文件select idgetStudent1 resultMapStudentTeacher1 SELECT student.id,student.Sname,teacher.Tname FROM student LEFT JOIN teacher on student.t_id teacher.id /select resultMap idStudentTeacher1 typecom.qcby.entity.Student !-- 基础字段映射property实体属性column数据库列名 -- result propertyid columnid/ result propertySname columnSname/ result propertysex columnsex/ result propertyage columnage/ result propertyt_id columnt_id/ !-- association处理一对一/多对一的关联对象 -- association propertyteacher javaTypecom.qcby.entity.Teacher result propertyid columnid/ result propertyTname columnTname/ /association /resultMap代码解释编写LEFT JOIN联表 SQL一次性查询学生和老师数据resultMap自定义结果映射association封装 Teacher 对象。方式二分步查询实体类无需改动Mapper 映射文件!-- 第一步查询所有学生 -- select id getStudent resultMapStudentTeacher select * from student; /select !-- 结果映射关联第二步查询 -- resultMap idStudentTeacher typecom.qcby.entity.Student result propertyid columnid/ result propertySname columnSname/ result propertysex columnsex/ result propertyage columnage/ result propertyt_id columnt_id/ !-- select调用第二步查询的id column传递给第二步查询的参数学生的t_id -- association propertyteacher columnt_id javaTypecom.qcby.entity.Teacher selectgetTeacher/ /resultMap !-- 第二步根据id查询老师 -- select idgetTeacher resultTypecom.qcby.entity.Teacher select * from teacher where id #{t_id}; /select代码解释将复杂联表拆分为两条单表查询第一步查学生第二步根据学生的t_id查老师实现解耦。三、一对多映射业务场景查询老师信息同时获取名下所有学生一个老师对应多个学生方式一嵌套结果联表查询实体类定义public class Student { private Integer id; private String Sname; private String sex; private Integer age; private Integer t_id; } public class Teacher { private Integer id; private String Tname; private ListStudent students; // 学生集合实现一对多映射 }代码解释Teacher 类中添加ListStudent集合用于封装该老师名下的所有学生。Mapper 映射文件select idgetTeacher resultMapTeacherStudent SELECT teacher.id,teacher.Tname,student.Sname FROM teacher LEFT JOIN student on student.t_id teacher.id /select resultMap idTeacherStudent typecom.qcby.entity.Teacher result propertyid columnid/ result propertyTname columnTname/ !-- collection处理一对多的关联集合 -- !-- ofType指定集合中泛型的类型 -- collection propertystudents ofTypecom.qcby.entity.Student result propertySname columnSname/ /collection /resultMap代码解释联表查询老师和学生数据collection将重复的学生数据封装为 List 集合。方式二分步查询!-- 第一步查询所有老师 -- select idgetTeacher resultMapTeacherStudent2 select * from teacher /select !-- 结果映射关联第二步查询 -- resultMap idTeacherStudent2 typecom.qcby.entity.Teacher collection propertystudents columnid ofTypecom.qcby.entity.Student selectgetStudentByTeacherId / /resultMap !-- 第二步根据老师id查询学生 -- select idgetStudentByTeacherId resultTypecom.qcby.entity.Student select * from student where t_id #{t_id} /select代码解释第一步查老师第二步根据老师id查询对应学生通过collection实现一对多分步映射。四、MyBatis 延迟加载策略1. 策略作用联表查询一次性加载全部数据分步查询可配合延迟加载实现按需查询减少无效 SQL 执行优化性能仅分步查询支持。2. 全局配置settings !-- 开启延迟加载总开关 -- setting namelazyLoadingEnabled valuetrue/ !-- 关闭积极加载实现真正的按需加载 -- setting nameaggressiveLazyLoading valuefalse/ /settings代码解释在 MyBatis 核心配置文件中配置全局生效。3. 局部加载控制association propertyteacher columnt_id javaTypecom.qcby.entity.Teacher selectcom.qcby.dao.TeacherDao.getTeacher fetchTypelazy !-- lazy延迟加载eager立即加载 -- /代码解释fetchType优先级高于全局配置可单独控制某个关联的加载模式。4. 接口与映射补充// TeacherDao接口 Teacher getTeacher(Integer id);!-- TeacherDao.xml -- select idgetTeacher resultTypecom.qcby.entity.Teacher parameterTypejava.lang.Integer select * from teacher where id #{t_id}; /select代码解释定义教师查询接口供分步查询调用。5. 测试效果仅访问学生基础属性 →不触发老师查询Test public void getStudent(){ ListStudent student mapper.getStudent(); for (Student student1:student) { System.out.println(student1.getSex()); } }访问关联老师属性 →触发延迟查询Test public void getStudent(){ ListStudent student mapper.getStudent(); for (Student student1:student) { System.out.println(student1.getTeacher().getTname()); } }