MyBatis关联映射
前言
在MyBatis开发中,关联查询 是处理实体间关系(如学生-老师、订单-用户)的核心场景。本文以「学生-老师」双向关联为例,梳理MyBatis中多对一(学生→老师) 和一对多(老师→学生) 的实现方式,以及分步查询、懒加载等关键知识点,结合实战代码拆解核心逻辑。
一、基础环境准备
1. 核心实体类
Student:学生实体,包含teacher属性(多对一关联老师)Teacher:老师实体,包含students集合属性(一对多关联学生)StudentTeacher:组合实体(用于直接映射联表查询结果)
核心代码片段(Student.java):
java
public class Student {
private Integer id;
private String name;
private String sex;
private Integer age;
private Integer t_id; // 关联老师的外键
private Teacher teacher; // 多对一关联的老师对象
// getter/setter/toString 省略
}
核心代码片段(Teacher.java):
java
public class Teacher {
private Integer id;
private String Tname;
public List<Student> students; // 一对多关联的学生集合
// getter/setter/toString 省略
}
2. MyBatis核心配置(SqlMapConfig.xml)
重点配置:懒加载、日志、数据源、Mapper扫描
xml
<configuration>
<!-- 日志配置:打印SQL,方便调试 -->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
<!-- 懒加载核心配置 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<!-- 数据源配置 -->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- Mapper扫描:包扫描方式(推荐) -->
<mappers>
<package name="com.qcby.dao"/>
</mappers>
</configuration>
二、多对一查询(学生关联老师)
场景:查询学生列表,同时关联查询每个学生的老师信息
方式1:联表查询 + ResultMap映射
核心文件:StudentMapper.xml
xml
<mapper namespace="com.qcby.dao.StudentDao">
<!-- 联表查询:学生+老师 -->
<select id="findStudentsTeacher" resultMap="StudentsTeacher">
select student.* ,teacher.Tname from student left join teacher on student.t_id = teacher.id
</select>
<!-- 自定义ResultMap:处理多对一关联 -->
<resultMap id="StudentsTeacher" type="com.qcby.entity.Student">
<!-- 学生基础字段映射 -->
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="t_id" column="t_id"/>
<!-- 多对一关联:association 标签 -->
<association property="teacher" javaType="com.qcby.entity.Teacher">
<result property="id" column="id"/> <!-- 老师ID -->
<result property="Tname" column="Tname"/> <!-- 老师姓名 -->
</association>
</resultMap>
</mapper>
方式2:分步查询 + 懒加载(性能优化)
核心逻辑:先查学生,再按需查老师(懒加载),避免联表查询的性能损耗。
xml
<mapper namespace="com.qcby.dao.StudentDao">
<!-- 第一步:查询所有学生 -->
<select id="findStudentsTeacher2" resultMap="StudentsTeacher2">
select * from student
</select>
<!-- 第二步:通过t_id查询老师(懒加载) -->
<resultMap id="StudentsTeacher2" type="com.qcby.entity.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="t_id" column="t_id"/>
<!-- association 分步查询 -->
<association
property="teacher" <!-- 关联的属性名 -->
select="getTeacherById" <!-- 第二步查询的SQL ID -->
column="t_id" <!-- 传递给第二步的参数(学生的t_id) -->
javaType="com.qcby.entity.Teacher"
fetchType="lazy"/> <!-- 懒加载(全局开启后,可省略) -->
</resultMap>
<!-- 第二步SQL:根据ID查老师 -->
<select id="getTeacherById" resultType="com.qcby.entity.Teacher">
select * from teacher where id = #{id}
</select>
</mapper>
测试代码(StudentTest.java)
java
@Test
public void findAll2(){
List<Student> studentsTeacher = mapper.findStudentsTeacher2();
for (Student student : studentsTeacher) {
System.out.println(student); // 仅打印学生基础信息时,不会触发老师的查询(懒加载)
// 当调用student.getTeacher()时,才会执行第二步SQL
}
}
三、一对多查询(老师关联学生)
场景:查询老师列表,同时关联查询每个老师的所有学生
方式1:联表查询 + ResultMap映射
核心文件:TeacherMapper.xml
xml
<mapper namespace="com.qcby.dao.TeacherDao">
<select id="findTeacherStudent" resultMap="TeacherStudents">
select teacher.*,student.name,student.sex,student.age from student left join teacher on student.t_id=teacher.id
</select>
<!-- 自定义ResultMap:处理一对多关联 -->
<resultMap id="TeacherStudents" type="com.qcby.entity.Teacher">
<result property="id" column="id"/>
<result property="Tname" column="Tname"/>
<!-- 一对多关联:collection 标签 -->
<collection property="students" ofType="com.qcby.entity.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="t_id" column="t_id"/>
</collection>
</resultMap>
</mapper>
方式2:分步查询 + 懒加载
xml
<mapper namespace="com.qcby.dao.TeacherDao">
<!-- 第一步:查询所有老师 -->
<select id="findTeacherStudent2" resultMap="TeacherStudents2">
select * from teacher
</select>
<resultMap id="TeacherStudents2" type="com.qcby.entity.Teacher">
<result property="id" column="id"/>
<result property="Tname" column="Tname"/>
<!-- collection 分步查询 -->
<collection
property="students"
select="getStudentById" <!-- 第二步查询的SQL ID -->
column="id" <!-- 传递给第二步的参数(老师ID) -->
ofType="com.qcby.entity.Student"
fetchType="lazy"/> <!-- 懒加载 -->
</resultMap>
<!-- 第二步SQL:根据老师ID查学生 -->
<select id="getStudentById" resultType="com.qcby.entity.Student">
select * from student where t_id = #{id}
</select>
</mapper>
测试代码(TeacherTest.java)
java
@Test
public void testFindTeacherStudent2() {
List<Teacher> teachers = mapper.findTeacherStudent2();
for (Teacher teacher : teachers) {
System.out.println(teacher.getTName()); // 仅打印老师姓名,不触发学生查询
// 调用teacher.getStudents()时,才会执行第二步SQL
}
}
四、拓展:直接映射到组合实体(StudentTeacher)
场景:无需封装对象关联,直接将联表结果映射到自定义实体。
核心文件:StudentTeacher.xml
xml
<mapper namespace="com.qcby.dao.StudentTeacherDao">
<select id="findAll" resultMap="sssss">
select student.*,teacher.* from student left join teacher on student.t_id=teacher.id;
</select>
<resultMap id="sssss" type="com.qcby.entity.StudentTeacher">
<result property="id" column="id"/> <!-- 学生ID -->
<result property="name" column="name"/> <!-- 学生姓名 -->
<result property="age" column="age"/> <!-- 学生年龄 -->
<result property="sex" column="sex"/> <!-- 学生性别 -->
<result property="t_id" column="t_id"/> <!-- 外键 -->
<result property="Tid" column="id(1)"/> <!-- 老师ID(解决字段冲突) -->
<result property="Tname" column="Tname"/> <!-- 老师姓名 -->
</resultMap>
</mapper>
五、关键注意事项
- ResultMap核心标签
- 多对一:
<association>+javaType(指定关联对象类型) - 一对多:
<collection>+ofType(指定集合元素类型)
- 多对一:
- 懒加载生效条件
- 全局配置
lazyLoadingEnabled=true+aggressiveLazyLoading=false fetchType="lazy"(局部覆盖全局,可选)
- 全局配置
- 字段冲突
- 联表查询时,若多张表有同名字段(如
id),需通过别名/特殊映射(如id(1))区分
- 联表查询时,若多张表有同名字段(如
- Mapper映射规则
namespace必须与Dao接口全类名一致- SQL的
id必须与Dao接口方法名一致
- 资源释放
- 测试代码中通过
@Before初始化SqlSession,@After关闭资源,避免连接泄漏
- 测试代码中通过
六、总结
MyBatis关联查询的核心是ResultMap的灵活使用:
- 联表查询:适合简单场景,一次SQL获取所有数据,但性能可能较差;
- 分步查询+懒加载:适合复杂场景,按需加载关联数据,减少无效查询,提升性能;
- 多对一用
association,一对多用collection,核心是明确「关联属性」和「数据类型」。
结合实战代码反复调试(重点看日志打印的SQL),能快速掌握关联查询的核心逻辑,应对日常开发中的实体关联场景。# MyBatis关联查询实战笔记:多对一与一对多处理
前言
在MyBatis开发中,关联查询 是处理实体间关系(如学生-老师、订单-用户)的核心场景。本文以「学生-老师」双向关联为例,梳理MyBatis中多对一(学生→老师) 和一对多(老师→学生) 的实现方式,以及分步查询、懒加载等关键知识点,结合实战代码拆解核心逻辑。
一、基础环境准备
1. 核心实体类
Student:学生实体,包含teacher属性(多对一关联老师)Teacher:老师实体,包含students集合属性(一对多关联学生)StudentTeacher:组合实体(用于直接映射联表查询结果)
核心代码片段(Student.java):
java
public class Student {
private Integer id;
private String name;
private String sex;
private Integer age;
private Integer t_id; // 关联老师的外键
private Teacher teacher; // 多对一关联的老师对象
// getter/setter/toString 省略
}
核心代码片段(Teacher.java):
java
public class Teacher {
private Integer id;
private String Tname;
public List<Student> students; // 一对多关联的学生集合
// getter/setter/toString 省略
}
2. MyBatis核心配置(SqlMapConfig.xml)
重点配置:懒加载、日志、数据源、Mapper扫描
xml
<configuration>
<!-- 日志配置:打印SQL,方便调试 -->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
<!-- 懒加载核心配置 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<!-- 数据源配置 -->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- Mapper扫描:包扫描方式(推荐) -->
<mappers>
<package name="com.qcby.dao"/>
</mappers>
</configuration>
二、多对一查询(学生关联老师)
场景:查询学生列表,同时关联查询每个学生的老师信息
方式1:联表查询 + ResultMap映射
核心文件:StudentMapper.xml
xml
<mapper namespace="com.qcby.dao.StudentDao">
<!-- 联表查询:学生+老师 -->
<select id="findStudentsTeacher" resultMap="StudentsTeacher">
select student.* ,teacher.Tname from student left join teacher on student.t_id = teacher.id
</select>
<!-- 自定义ResultMap:处理多对一关联 -->
<resultMap id="StudentsTeacher" type="com.qcby.entity.Student">
<!-- 学生基础字段映射 -->
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="t_id" column="t_id"/>
<!-- 多对一关联:association 标签 -->
<association property="teacher" javaType="com.qcby.entity.Teacher">
<result property="id" column="id"/> <!-- 老师ID -->
<result property="Tname" column="Tname"/> <!-- 老师姓名 -->
</association>
</resultMap>
</mapper>
方式2:分步查询 + 懒加载(性能优化)
核心逻辑:先查学生,再按需查老师(懒加载),避免联表查询的性能损耗。
xml
<mapper namespace="com.qcby.dao.StudentDao">
<!-- 第一步:查询所有学生 -->
<select id="findStudentsTeacher2" resultMap="StudentsTeacher2">
select * from student
</select>
<!-- 第二步:通过t_id查询老师(懒加载) -->
<resultMap id="StudentsTeacher2" type="com.qcby.entity.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="t_id" column="t_id"/>
<!-- association 分步查询 -->
<association
property="teacher" <!-- 关联的属性名 -->
select="getTeacherById" <!-- 第二步查询的SQL ID -->
column="t_id" <!-- 传递给第二步的参数(学生的t_id) -->
javaType="com.qcby.entity.Teacher"
fetchType="lazy"/> <!-- 懒加载(全局开启后,可省略) -->
</resultMap>
<!-- 第二步SQL:根据ID查老师 -->
<select id="getTeacherById" resultType="com.qcby.entity.Teacher">
select * from teacher where id = #{id}
</select>
</mapper>
测试代码(StudentTest.java)
java
@Test
public void findAll2(){
List<Student> studentsTeacher = mapper.findStudentsTeacher2();
for (Student student : studentsTeacher) {
System.out.println(student); // 仅打印学生基础信息时,不会触发老师的查询(懒加载)
// 当调用student.getTeacher()时,才会执行第二步SQL
}
}
三、一对多查询(老师关联学生)
场景:查询老师列表,同时关联查询每个老师的所有学生
方式1:联表查询 + ResultMap映射
核心文件:TeacherMapper.xml
xml
<mapper namespace="com.qcby.dao.TeacherDao">
<select id="findTeacherStudent" resultMap="TeacherStudents">
select teacher.*,student.name,student.sex,student.age from student left join teacher on student.t_id=teacher.id
</select>
<!-- 自定义ResultMap:处理一对多关联 -->
<resultMap id="TeacherStudents" type="com.qcby.entity.Teacher">
<result property="id" column="id"/>
<result property="Tname" column="Tname"/>
<!-- 一对多关联:collection 标签 -->
<collection property="students" ofType="com.qcby.entity.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="t_id" column="t_id"/>
</collection>
</resultMap>
</mapper>
方式2:分步查询 + 懒加载
xml
<mapper namespace="com.qcby.dao.TeacherDao">
<!-- 第一步:查询所有老师 -->
<select id="findTeacherStudent2" resultMap="TeacherStudents2">
select * from teacher
</select>
<resultMap id="TeacherStudents2" type="com.qcby.entity.Teacher">
<result property="id" column="id"/>
<result property="Tname" column="Tname"/>
<!-- collection 分步查询 -->
<collection
property="students"
select="getStudentById" <!-- 第二步查询的SQL ID -->
column="id" <!-- 传递给第二步的参数(老师ID) -->
ofType="com.qcby.entity.Student"
fetchType="lazy"/> <!-- 懒加载 -->
</resultMap>
<!-- 第二步SQL:根据老师ID查学生 -->
<select id="getStudentById" resultType="com.qcby.entity.Student">
select * from student where t_id = #{id}
</select>
</mapper>
测试代码(TeacherTest.java)
java
@Test
public void testFindTeacherStudent2() {
List<Teacher> teachers = mapper.findTeacherStudent2();
for (Teacher teacher : teachers) {
System.out.println(teacher.getTName()); // 仅打印老师姓名,不触发学生查询
// 调用teacher.getStudents()时,才会执行第二步SQL
}
}
四、拓展:直接映射到组合实体(StudentTeacher)
场景:无需封装对象关联,直接将联表结果映射到自定义实体。
核心文件:StudentTeacher.xml
xml
<mapper namespace="com.qcby.dao.StudentTeacherDao">
<select id="findAll" resultMap="sssss">
select student.*,teacher.* from student left join teacher on student.t_id=teacher.id;
</select>
<resultMap id="sssss" type="com.qcby.entity.StudentTeacher">
<result property="id" column="id"/> <!-- 学生ID -->
<result property="name" column="name"/> <!-- 学生姓名 -->
<result property="age" column="age"/> <!-- 学生年龄 -->
<result property="sex" column="sex"/> <!-- 学生性别 -->
<result property="t_id" column="t_id"/> <!-- 外键 -->
<result property="Tid" column="id(1)"/> <!-- 老师ID(解决字段冲突) -->
<result property="Tname" column="Tname"/> <!-- 老师姓名 -->
</resultMap>
</mapper>
五、关键注意事项
- ResultMap核心标签
- 多对一:
<association>+javaType(指定关联对象类型) - 一对多:
<collection>+ofType(指定集合元素类型)
- 多对一:
- 懒加载生效条件
- 全局配置
lazyLoadingEnabled=true+aggressiveLazyLoading=false fetchType="lazy"(局部覆盖全局,可选)
- 全局配置
- 字段冲突
- 联表查询时,若多张表有同名字段(如
id),需通过别名/特殊映射(如id(1))区分
- 联表查询时,若多张表有同名字段(如
- Mapper映射规则
namespace必须与Dao接口全类名一致- SQL的
id必须与Dao接口方法名一致
- 资源释放
- 测试代码中通过
@Before初始化SqlSession,@After关闭资源,避免连接泄漏
- 测试代码中通过
六、总结
MyBatis关联查询的核心是ResultMap的灵活使用:
- 联表查询:适合简单场景,一次SQL获取所有数据,但性能可能较差;
- 分步查询+懒加载:适合复杂场景,按需加载关联数据,减少无效查询,提升性能;
- 多对一用
association,一对多用collection,核心是明确「关联属性」和「数据类型」。
结合实战代码反复调试(重点看日志打印的SQL),能快速掌握关联查询的核心逻辑,应对日常开发中的实体关联场景。