MyBatis关联映射

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&amp;serverTimezone=UTC&amp;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>

五、关键注意事项

  1. ResultMap核心标签
    • 多对一:<association> + javaType(指定关联对象类型)
    • 一对多:<collection> + ofType(指定集合元素类型)
  2. 懒加载生效条件
    • 全局配置lazyLoadingEnabled=true + aggressiveLazyLoading=false
    • fetchType="lazy"(局部覆盖全局,可选)
  3. 字段冲突
    • 联表查询时,若多张表有同名字段(如id),需通过别名/特殊映射(如id(1))区分
  4. Mapper映射规则
    • namespace必须与Dao接口全类名一致
    • SQL的id必须与Dao接口方法名一致
  5. 资源释放
    • 测试代码中通过@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&amp;serverTimezone=UTC&amp;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>

五、关键注意事项

  1. ResultMap核心标签
    • 多对一:<association> + javaType(指定关联对象类型)
    • 一对多:<collection> + ofType(指定集合元素类型)
  2. 懒加载生效条件
    • 全局配置lazyLoadingEnabled=true + aggressiveLazyLoading=false
    • fetchType="lazy"(局部覆盖全局,可选)
  3. 字段冲突
    • 联表查询时,若多张表有同名字段(如id),需通过别名/特殊映射(如id(1))区分
  4. Mapper映射规则
    • namespace必须与Dao接口全类名一致
    • SQL的id必须与Dao接口方法名一致
  5. 资源释放
    • 测试代码中通过@Before初始化SqlSession,@After关闭资源,避免连接泄漏

六、总结

MyBatis关联查询的核心是ResultMap的灵活使用:

  • 联表查询:适合简单场景,一次SQL获取所有数据,但性能可能较差;
  • 分步查询+懒加载:适合复杂场景,按需加载关联数据,减少无效查询,提升性能;
  • 多对一用association,一对多用collection,核心是明确「关联属性」和「数据类型」。

结合实战代码反复调试(重点看日志打印的SQL),能快速掌握关联查询的核心逻辑,应对日常开发中的实体关联场景。

相关推荐
许彰午8 小时前
38_Java设计模式之装饰器模式
java·设计模式·装饰器模式
折哥的程序人生 · 物流技术专研8 小时前
Java 23 种设计模式:从踩坑到精通 | 组合模式 —— 树形结构处理,部分与整体一视同仁
java·组合模式·java面试·springsecurity·结构型模式·java设计模式·从踩坑到精通
郝学胜-神的一滴8 小时前
完全二叉树与堆底层原理深度剖析 | 手写C++大顶堆实现
java·开发语言·数据结构·c++·python·算法
农民小飞侠8 小时前
[leetcode] 165. Compare Version Numbers
java·算法·leetcode
砍材农夫8 小时前
物联网实战|Spring Boot + Netty 搭建 MQTT 消息路由与流转层
java·spring boot·后端·物联网·spring
黄毛火烧雪下9 小时前
Java 基础笔记:文件、递归与字符编码
java·开发语言·笔记
学计算机的计算基9 小时前
链表算法上篇:LeetCode 206/234/141/142/160/21 题解与易错点
java·笔记·算法·链表
信也科技布道师9 小时前
从Istio 503 NC 错误深入理解 Mesh 路由全链路原理
java·服务器·网络
swordbob9 小时前
3 大 I/O 模型BIO / NIO / AIO
java·linux·spring
Pluto_CSND9 小时前
Cron表达式使用说明
java