全部笔记All notes

Web程序设计笔记13——第九章:MyBatis 的关系映射

阅读 10m 37s10m 37s read

MyBatis 关系映射完整指南

概述

在实际开发中,数据库表之间往往存在着各种关系。MyBatis 提供了强大的关系映射功能,可以优雅地处理表之间的一对一、一对多、多对多等复杂关系。本章将详细介绍如何在 MyBatis 中实现各种关系映射。

💡 核心概念:

  • 一对一(One-to-One):一个实体对应另一个实体,如人与身份证
  • 一对多(One-to-Many):一个实体对应多个其他实体,如部门与员工
  • 多对多(Many-to-Many):多个实体对应多个其他实体,如学生与课程

关系映射基础

映射方式对比

MyBatis 提供了两种主要的关联查询方式:

映射方式特点SQL 数量复杂度适用场景
嵌套查询分步查询,支持延迟加载多条简单SQL低关联数据可选加载
嵌套结果一次查询,立即加载一条复杂SQL高需要全部关联数据

核心标签说明

<!-- association: 用于一对一和多对一映射 -->
<association property="属性名" javaType="Java类型">
    <!-- 映射配置 -->
</association>

<!-- collection: 用于一对多和多对多映射 -->
<collection property="属性名" ofType="集合元素类型">
    <!-- 映射配置 -->
</collection>

一对一关系映射

场景描述

以人员(Person)和身份证(IdCard)的关系为例,一个人只有一个身份证,一个身份证只属于一个人。

数据库设计

-- 身份证表
CREATE TABLE tb_idcard (
    id INT PRIMARY KEY AUTO_INCREMENT,
    code VARCHAR(18) UNIQUE NOT NULL COMMENT '身份证号码'
);

-- 人员表
CREATE TABLE tb_person (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(32) NOT NULL COMMENT '姓名',
    age INT COMMENT '年龄',
    sex VARCHAR(8) COMMENT '性别',
    card_id INT UNIQUE COMMENT '身份证ID',
    FOREIGN KEY(card_id) REFERENCES tb_idcard(id)
);

-- 插入测试数据
INSERT INTO tb_idcard(code) VALUES 
('110101199001011234'),
('110102199002021234');

INSERT INTO tb_person(name, age, sex, card_id) VALUES 
('张三', 25, '男', 1),
('李四', 23, '女', 2);

实体类设计

// IdCard.java
public class IdCard {
    private Integer id;
    private String code;
    
    // getter/setter/toString 省略
}

// Person.java
public class Person {
    private Integer id;
    private String name;
    private Integer age;
    private String sex;
    private IdCard card;  // 一对一关系
    
    // getter/setter/toString 省略
}

方式一:嵌套查询(分步查询)

<!-- IdCardMapper.xml -->
<mapper namespace="com.example.mapper.IdCardMapper">
    <!-- 根据ID查询身份证信息 -->
    <select id="selectIdCardById" resultType="IdCard">
        SELECT * FROM tb_idcard WHERE id = #{id}
    </select>
</mapper>

<!-- PersonMapper.xml -->
<mapper namespace="com.example.mapper.PersonMapper">
    <resultMap id="personResultMap" type="Person">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <result property="sex" column="sex"/>
        <!-- 一对一关联:嵌套查询 -->
        <association property="card" column="card_id" 
                    javaType="IdCard"
                    select="com.example.mapper.IdCardMapper.selectIdCardById"/>
    </resultMap>
    
    <select id="selectPersonById" resultMap="personResultMap">
        SELECT * FROM tb_person WHERE id = #{id}
    </select>
</mapper>

方式二:嵌套结果(联合查询)

<!-- PersonMapper.xml -->
<mapper namespace="com.example.mapper.PersonMapper">
    <resultMap id="personResultMap2" type="Person">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <result property="sex" column="sex"/>
        <!-- 一对一关联:嵌套结果 -->
        <association property="card" javaType="IdCard">
            <id property="id" column="card_id"/>
            <result property="code" column="code"/>
        </association>
    </resultMap>
    
    <select id="selectPersonWithCard" resultMap="personResultMap2">
        SELECT p.*, c.code
        FROM tb_person p
        LEFT JOIN tb_idcard c ON p.card_id = c.id
        WHERE p.id = #{id}
    </select>
</mapper>

测试代码

@Test
public void testOneToOne() {
    try (SqlSession session = sqlSessionFactory.openSession()) {
        PersonMapper mapper = session.getMapper(PersonMapper.class);
        
        // 测试嵌套查询
        Person person1 = mapper.selectPersonById(1);
        System.out.println("嵌套查询结果:" + person1);
        
        // 测试嵌套结果
        Person person2 = mapper.selectPersonWithCard(1);
        System.out.println("嵌套结果查询:" + person2);
    }
}

一对多关系映射

场景描述

以部门(Department)和员工(Employee)的关系为例,一个部门有多个员工,一个员工只属于一个部门。

数据库设计

-- 部门表
CREATE TABLE tb_department (
    id INT PRIMARY KEY AUTO_INCREMENT,
    dept_name VARCHAR(50) NOT NULL COMMENT '部门名称',
    location VARCHAR(100) COMMENT '部门位置'
);

-- 员工表
CREATE TABLE tb_employee (
    id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(32) NOT NULL COMMENT '员工姓名',
    position VARCHAR(50) COMMENT '职位',
    salary DECIMAL(10,2) COMMENT '薪资',
    dept_id INT COMMENT '部门ID',
    FOREIGN KEY(dept_id) REFERENCES tb_department(id)
);

-- 插入测试数据
INSERT INTO tb_department(dept_name, location) VALUES 
('技术部', '北京'),
('销售部', '上海'),
('人事部', '广州');

INSERT INTO tb_employee(emp_name, position, salary, dept_id) VALUES 
('王五', 'Java工程师', 15000, 1),
('赵六', '前端工程师', 13000, 1),
('钱七', 'Python工程师', 16000, 1),
('孙八', '销售经理', 12000, 2),
('周九', '销售专员', 8000, 2);

实体类设计

// Department.java
public class Department {
    private Integer id;
    private String deptName;
    private String location;
    private List<Employee> employees;  // 一对多关系
    
    // getter/setter/toString 省略
}

// Employee.java
public class Employee {
    private Integer id;
    private String empName;
    private String position;
    private BigDecimal salary;
    private Integer deptId;
    private Department department;  // 多对一关系
    
    // getter/setter/toString 省略
}

一对多映射:嵌套查询

<!-- EmployeeMapper.xml -->
<mapper namespace="com.example.mapper.EmployeeMapper">
    <!-- 根据部门ID查询员工列表 -->
    <select id="selectEmployeesByDeptId" resultType="Employee">
        SELECT * FROM tb_employee WHERE dept_id = #{deptId}
    </select>
</mapper>

<!-- DepartmentMapper.xml -->
<mapper namespace="com.example.mapper.DepartmentMapper">
    <resultMap id="deptWithEmployeesMap" type="Department">
        <id property="id" column="id"/>
        <result property="deptName" column="dept_name"/>
        <result property="location" column="location"/>
        <!-- 一对多关联:嵌套查询 -->
        <collection property="employees" column="id"
                   ofType="Employee"
                   select="com.example.mapper.EmployeeMapper.selectEmployeesByDeptId"/>
    </resultMap>
    
    <select id="selectDeptWithEmployees" resultMap="deptWithEmployeesMap">
        SELECT * FROM tb_department WHERE id = #{id}
    </select>
</mapper>

一对多映射:嵌套结果

<!-- DepartmentMapper.xml -->
<mapper namespace="com.example.mapper.DepartmentMapper">
    <resultMap id="deptWithEmployeesMap2" type="Department">
        <id property="id" column="dept_id"/>
        <result property="deptName" column="dept_name"/>
        <result property="location" column="location"/>
        <!-- 一对多关联:嵌套结果 -->
        <collection property="employees" ofType="Employee">
            <id property="id" column="emp_id"/>
            <result property="empName" column="emp_name"/>
            <result property="position" column="position"/>
            <result property="salary" column="salary"/>
            <result property="deptId" column="dept_id"/>
        </collection>
    </resultMap>
    
    <select id="selectDeptWithEmployees2" resultMap="deptWithEmployeesMap2">
        SELECT 
            d.id as dept_id,
            d.dept_name,
            d.location,
            e.id as emp_id,
            e.emp_name,
            e.position,
            e.salary
        FROM tb_department d
        LEFT JOIN tb_employee e ON d.id = e.dept_id
        WHERE d.id = #{id}
    </select>
</mapper>

多对一映射

<!-- EmployeeMapper.xml -->
<mapper namespace="com.example.mapper.EmployeeMapper">
    <resultMap id="empWithDeptMap" type="Employee">
        <id property="id" column="id"/>
        <result property="empName" column="emp_name"/>
        <result property="position" column="position"/>
        <result property="salary" column="salary"/>
        <!-- 多对一关联 -->
        <association property="department" javaType="Department">
            <id property="id" column="dept_id"/>
            <result property="deptName" column="dept_name"/>
            <result property="location" column="location"/>
        </association>
    </resultMap>
    
    <select id="selectEmpWithDept" resultMap="empWithDeptMap">
        SELECT 
            e.*,
            d.dept_name,
            d.location
        FROM tb_employee e
        LEFT JOIN tb_department d ON e.dept_id = d.id
        WHERE e.id = #{id}
    </select>
</mapper>

测试代码

@Test
public void testOneToMany() {
    try (SqlSession session = sqlSessionFactory.openSession()) {
        DepartmentMapper deptMapper = session.getMapper(DepartmentMapper.class);
        
        // 测试一对多查询
        Department dept = deptMapper.selectDeptWithEmployees(1);
        System.out.println("部门:" + dept.getDeptName());
        System.out.println("员工数量:" + dept.getEmployees().size());
        dept.getEmployees().forEach(System.out::println);
    }
}

@Test
public void testManyToOne() {
    try (SqlSession session = sqlSessionFactory.openSession()) {
        EmployeeMapper empMapper = session.getMapper(EmployeeMapper.class);
        
        // 测试多对一查询
        Employee emp = empMapper.selectEmpWithDept(1);
        System.out.println("员工:" + emp.getEmpName());
        System.out.println("所属部门:" + emp.getDepartment().getDeptName());
    }
}

多对多关系映射

场景描述

以学生(Student)和课程(Course)的关系为例,一个学生可以选多门课程,一门课程可以被多个学生选择。

数据库设计

-- 学生表
CREATE TABLE tb_student (
    id INT PRIMARY KEY AUTO_INCREMENT,
    stu_name VARCHAR(32) NOT NULL COMMENT '学生姓名',
    stu_no VARCHAR(20) UNIQUE COMMENT '学号',
    grade VARCHAR(20) COMMENT '年级'
);

-- 课程表
CREATE TABLE tb_course (
    id INT PRIMARY KEY AUTO_INCREMENT,
    course_name VARCHAR(50) NOT NULL COMMENT '课程名称',
    credit INT COMMENT '学分',
    teacher VARCHAR(32) COMMENT '授课教师'
);

-- 选课表(中间表)
CREATE TABLE tb_student_course (
    student_id INT NOT NULL,
    course_id INT NOT NULL,
    score DECIMAL(5,2) COMMENT '成绩',
    PRIMARY KEY(student_id, course_id),
    FOREIGN KEY(student_id) REFERENCES tb_student(id),
    FOREIGN KEY(course_id) REFERENCES tb_course(id)
);

-- 插入测试数据
INSERT INTO tb_student(stu_name, stu_no, grade) VALUES 
('小明', '2021001', '大一'),
('小红', '2021002', '大一'),
('小刚', '2021003', '大二');

INSERT INTO tb_course(course_name, credit, teacher) VALUES 
('Java程序设计', 4, '张老师'),
('数据库原理', 3, '李老师'),
('Web开发', 3, '王老师'),
('数据结构', 4, '赵老师');

INSERT INTO tb_student_course(student_id, course_id, score) VALUES 
(1, 1, 85.5),
(1, 2, 90.0),
(1, 3, 88.5),
(2, 1, 92.0),
(2, 3, 87.5),
(3, 2, 89.0),
(3, 4, 91.5);

实体类设计

// Student.java
public class Student {
    private Integer id;
    private String stuName;
    private String stuNo;
    private String grade;
    private List<Course> courses;  // 多对多关系
    
    // getter/setter/toString 省略
}

// Course.java
public class Course {
    private Integer id;
    private String courseName;
    private Integer credit;
    private String teacher;
    private List<Student> students;  // 多对多关系
    private BigDecimal score;  // 中间表的成绩字段
    
    // getter/setter/toString 省略
}

学生查询课程(多对多)

<!-- CourseMapper.xml -->
<mapper namespace="com.example.mapper.CourseMapper">
    <!-- 根据学生ID查询课程列表 -->
    <select id="selectCoursesByStudentId" resultType="Course">
        SELECT 
            c.*,
            sc.score
        FROM tb_course c
        INNER JOIN tb_student_course sc ON c.id = sc.course_id
        WHERE sc.student_id = #{studentId}
    </select>
</mapper>

<!-- StudentMapper.xml -->
<mapper namespace="com.example.mapper.StudentMapper">
    <!-- 方式一:嵌套查询 -->
    <resultMap id="studentWithCoursesMap" type="Student">
        <id property="id" column="id"/>
        <result property="stuName" column="stu_name"/>
        <result property="stuNo" column="stu_no"/>
        <result property="grade" column="grade"/>
        <collection property="courses" column="id"
                   ofType="Course"
                   select="com.example.mapper.CourseMapper.selectCoursesByStudentId"/>
    </resultMap>
    
    <select id="selectStudentWithCourses" resultMap="studentWithCoursesMap">
        SELECT * FROM tb_student WHERE id = #{id}
    </select>
    
    <!-- 方式二:嵌套结果 -->
    <resultMap id="studentWithCoursesMap2" type="Student">
        <id property="id" column="stu_id"/>
        <result property="stuName" column="stu_name"/>
        <result property="stuNo" column="stu_no"/>
        <result property="grade" column="grade"/>
        <collection property="courses" ofType="Course">
            <id property="id" column="course_id"/>
            <result property="courseName" column="course_name"/>
            <result property="credit" column="credit"/>
            <result property="teacher" column="teacher"/>
            <result property="score" column="score"/>
        </collection>
    </resultMap>
    
    <select id="selectStudentWithCourses2" resultMap="studentWithCoursesMap2">
        SELECT 
            s.id as stu_id,
            s.stu_name,
            s.stu_no,
            s.grade,
            c.id as course_id,
            c.course_name,
            c.credit,
            c.teacher,
            sc.score
        FROM tb_student s
        LEFT JOIN tb_student_course sc ON s.id = sc.student_id
        LEFT JOIN tb_course c ON sc.course_id = c.id
        WHERE s.id = #{id}
    </select>
</mapper>

课程查询学生(多对多)

<!-- CourseMapper.xml -->
<mapper namespace="com.example.mapper.CourseMapper">
    <resultMap id="courseWithStudentsMap" type="Course">
        <id property="id" column="course_id"/>
        <result property="courseName" column="course_name"/>
        <result property="credit" column="credit"/>
        <result property="teacher" column="teacher"/>
        <collection property="students" ofType="Student">
            <id property="id" column="stu_id"/>
            <result property="stuName" column="stu_name"/>
            <result property="stuNo" column="stu_no"/>
            <result property="grade" column="grade"/>
        </collection>
    </resultMap>
    
    <select id="selectCourseWithStudents" resultMap="courseWithStudentsMap">
        SELECT 
            c.id as course_id,
            c.course_name,
            c.credit,
            c.teacher,
            s.id as stu_id,
            s.stu_name,
            s.stu_no,
            s.grade
        FROM tb_course c
        LEFT JOIN tb_student_course sc ON c.id = sc.course_id
        LEFT JOIN tb_student s ON sc.student_id = s.id
        WHERE c.id = #{id}
    </select>
</mapper>

测试代码

@Test
public void testManyToMany() {
    try (SqlSession session = sqlSessionFactory.openSession()) {
        StudentMapper studentMapper = session.getMapper(StudentMapper.class);
        CourseMapper courseMapper = session.getMapper(CourseMapper.class);
        
        // 测试学生查询课程
        Student student = studentMapper.selectStudentWithCourses(1);
        System.out.println("学生:" + student.getStuName());
        System.out.println("选修课程数:" + student.getCourses().size());
        student.getCourses().forEach(course -> 
            System.out.println("  " + course.getCourseName() + " - 成绩:" + course.getScore())
        );
        
        System.out.println("\n------------------------\n");
        
        // 测试课程查询学生
        Course course = courseMapper.selectCourseWithStudents(1);
        System.out.println("课程:" + course.getCourseName());
        System.out.println("选课学生数:" + course.getStudents().size());
        course.getStudents().forEach(stu -> 
            System.out.println("  " + stu.getStuName() + " - " + stu.getGrade())
        );
    }
}

高级映射技巧

1. 延迟加载配置

<!-- mybatis-config.xml -->
<configuration>
    <settings>
        <!-- 开启延迟加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 关闭积极加载 -->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!-- 触发延迟加载的方法 -->
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
    </settings>
</configuration>

<!-- Mapper中配置 -->
<association property="department" 
            select="selectDepartment" 
            column="dept_id"
            fetchType="lazy"/>  <!-- 延迟加载 -->

2. 鉴别器(Discriminator)

用于根据某个字段的值来决定使用哪个映射规则。

<resultMap id="vehicleResultMap" type="Vehicle">
    <id property="id" column="id"/>
    <result property="brand" column="brand"/>
    <discriminator javaType="string" column="vehicle_type">
        <case value="CAR" resultType="Car">
            <result property="doorCount" column="door_count"/>
        </case>
        <case value="TRUCK" resultType="Truck">
            <result property="loadCapacity" column="load_capacity"/>
        </case>
    </discriminator>
</resultMap>

3. 多结果集处理

处理存储过程返回的多个结果集。

<select id="selectUserAndOrders" statementType="CALLABLE">
    {call getUserAndOrders(#{userId, mode=IN, jdbcType=INTEGER})}
</select>

<resultMap id="userResult" type="User">
    <!-- User映射 -->
</resultMap>

<resultMap id="orderResult" type="Order">
    <!-- Order映射 -->
</resultMap>

<!-- 结果集映射 -->
<resultSets>
    <resultSet name="users" resultMap="userResult"/>
    <resultSet name="orders" resultMap="orderResult"/>
</resultSets>

4. 构造方法映射

使用构造方法创建实例。

<resultMap id="userConstructorMap" type="User">
    <constructor>
        <idArg column="id" javaType="int"/>
        <arg column="username" javaType="string"/>
        <arg column="email" javaType="string"/>
    </constructor>
    <association property="profile" javaType="UserProfile">
        <!-- Profile映射 -->
    </association>
</resultMap>

性能优化

1. N+1 查询问题

问题描述

使用嵌套查询时,查询N个主记录会额外执行N次关联查询。

解决方案
<!-- 方案1:使用嵌套结果代替嵌套查询 -->
<select id="selectDeptWithEmployees" resultMap="deptWithEmpResultMap">
    SELECT d.*, e.*
    FROM department d
    LEFT JOIN employee e ON d.id = e.dept_id
</select>

<!-- 方案2:使用批量查询 -->
<select id="selectEmployeesByDeptIds" resultType="Employee">
    SELECT * FROM employee 
    WHERE dept_id IN
    <foreach collection="list" item="deptId" open="(" separator="," close=")">
        #{deptId}
    </foreach>
</select>

2. 合理使用缓存

<!-- 开启二级缓存 -->
<cache 
    eviction="LRU"
    flushInterval="60000"
    size="512"
    readOnly="true"/>

<!-- 针对特定查询关闭缓存 -->
<select id="selectRealTimeData" useCache="false">
    SELECT * FROM real_time_table
</select>

3. 查询优化技巧

// 只查询需要的字段
@Select("SELECT id, name FROM user WHERE id = #{id}")
User selectUserBasicInfo(int id);

// 分页查询
@Select("SELECT * FROM user LIMIT #{offset}, #{limit}")
List<User> selectUsersByPage(@Param("offset") int offset, 
                           @Param("limit") int limit);

// 使用索引字段查询
@Select("SELECT * FROM user WHERE email = #{email}")  // email有索引
User selectUserByEmail(String email);

最佳实践

1. 选择合适的映射方式

// 推荐:关联数据总是需要时,使用嵌套结果
public interface UserMapper {
    @Select("SELECT u.*, p.* FROM user u LEFT JOIN profile p ON u.id = p.user_id WHERE u.id = #{id}")
    @ResultMap("userWithProfileMap")
    User selectUserWithProfile(int id);
}

// 推荐:关联数据可选时,使用嵌套查询 + 延迟加载
public interface OrderMapper {
    @Select("SELECT * FROM orders WHERE id = #{id}")
    @ResultMap("orderWithLazyItemsMap")
    Order selectOrderWithLazyItems(int id);
}

2. 避免循环引用

// User.java
public class User {
    private List<Order> orders;
    // 不要在Order中再引用User,避免循环
}

// Order.java  
public class Order {
    private Integer userId;  // 只保存ID,不保存User对象
}

3. 合理设计ResultMap

<!-- 基础ResultMap,供继承使用 -->
<resultMap id="baseUserMap" type="User">
    <id property="id" column="user_id"/>
    <result property="username" column="username"/>
    <result property="email" column="email"/>
</resultMap>

<!-- 继承基础ResultMap -->
<resultMap id="userDetailMap" type="User" extends="baseUserMap">
    <association property="profile" resultMap="profileMap"/>
    <collection property="orders" resultMap="orderMap"/>
</resultMap>

4. 使用DTO优化传输

// UserDTO.java - 针对特定场景的数据传输对象
public class UserDTO {
    private Integer id;
    private String username;
    private String departmentName;  // 直接包含关联表的必要字段
    private Integer orderCount;     // 统计信息
}

// Mapper查询
@Select("SELECT u.id, u.username, d.name as departmentName, COUNT(o.id) as orderCount " +
        "FROM user u " +
        "LEFT JOIN department d ON u.dept_id = d.id " +
        "LEFT JOIN orders o ON u.id = o.user_id " +
        "GROUP BY u.id, u.username, d.name")
List<UserDTO> selectUserSummary();

常见问题

问题 1:关联查询结果为 null

原因分析:

  • 数据库中确实没有关联数据
  • ResultMap 配置错误
  • 列名映射错误

解决方案:

<!-- 检查列别名是否正确 -->
<resultMap id="userMap" type="User">
    <id property="id" column="user_id"/>  <!-- 确保列名正确 -->
    <association property="profile" javaType="Profile">
        <id property="id" column="profile_id"/>  <!-- 使用别名避免冲突 -->
    </association>
</resultMap>

<!-- SQL中使用别名 -->
<select id="selectUser" resultMap="userMap">
    SELECT 
        u.id as user_id,
        p.id as profile_id,
        p.avatar
    FROM user u
    LEFT JOIN profile p ON u.id = p.user_id
</select>

问题 2:集合映射重复数据

原因分析: 一对多查询时,主表数据会因为从表的多条记录而重复。

解决方案:

<!-- 确保主表有正确的ID映射 -->
<resultMap id="deptMap" type="Department">
    <id property="id" column="dept_id"/>  <!-- 必须配置ID -->
    <collection property="employees" ofType="Employee">
        <id property="id" column="emp_id"/>  <!-- 子表也要配置ID -->
    </collection>
</resultMap>

问题 3:延迟加载失效

原因分析:

  • 全局配置未开启
  • 在同一个SqlSession中已经加载过
  • 使用了触发加载的方法

解决方案:

// 正确使用延迟加载
try (SqlSession session = sqlSessionFactory.openSession()) {
    User user = session.selectOne("selectUser", 1);
    // 此时profile未加载
    
    // 访问关联对象时才加载
    UserProfile profile = user.getProfile();
}

// 避免在toString()中访问延迟加载属性
@Override
public String toString() {
    return "User{id=" + id + ", username=" + username + "}";
    // 不要包含 profile
}

问题 4:多对多查询性能问题

解决方案:

// 分页查询
public List<Student> selectStudentsWithCourses(int offset, int limit) {
    // 先查询学生
    List<Student> students = studentMapper.selectStudentsByPage(offset, limit);
    
    // 收集学生ID
    List<Integer> studentIds = students.stream()
        .map(Student::getId)
        .collect(Collectors.toList());
    
    // 批量查询课程
    List<Map<String, Object>> studentCourses = 
        courseMapper.selectCoursesByStudentIds(studentIds);
    
    // 组装结果
    // ...
}

总结

MyBatis 的关系映射功能强大而灵活,掌握好以下要点可以帮助我们更好地使用:

🎯 核心要点

  1. 理解两种映射方式:嵌套查询支持延迟加载,嵌套结果一次加载所有数据
  2. 正确使用标签:association 用于一对一/多对一,collection 用于一对多/多对多
  3. 注意性能问题:避免 N+1 查询,合理使用缓存
  4. 保持简单设计:避免过深的嵌套和循环引用

📊 选择建议

  • 简单关系:使用嵌套结果,一次查询获取所有数据
  • 复杂关系:使用嵌套查询 + 延迟加载,按需加载数据
  • 高性能要求:使用 DTO + 自定义查询,精确控制查询内容

相关文章

前后章节导航

MyBatis 核心技术

Spring 整合相关

SpringBoot 进阶学习

数据库设计与优化

性能优化相关

  • N+1 查询问题解决方案
  • MyBatis 缓存机制详解
  • 数据库索引优化策略
  • 复杂查询性能调优