Skip to content

MyBatis 关联查询详解

一对一、一对多、多对多、嵌套查询、嵌套结果


概述

MyBatis 通过 <association><collection> 标签支持复杂关系的映射查询。

查询方式对比

方式说明优点缺点
嵌套查询(N+1)先查主表,再查关联表SQL 简单执行多次查询
嵌套结果一次 JOIN 查询只查一次数据库SQL 复杂
自动映射配置 autoMapping简单需列名匹配

一对一关联

场景:订单 → 用户(一个订单属于一个用户)

表结构

sql
-- 用户表
CREATE TABLE user (
    id BIGINT PRIMARY KEY,
    user_name VARCHAR(50),
    email VARCHAR(100)
);

-- 订单表
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    order_no VARCHAR(50),
    user_id BIGINT,
    total_amount DECIMAL(10,2),
    FOREIGN KEY (user_id) REFERENCES user(id)
);

实体类

java
public class User {
    private Long id;
    private String name;
    private String email;
}

public class Order {
    private Long id;
    private String orderNo;
    private BigDecimal totalAmount;
    private User user;  // 一对一关联
}

方式一:嵌套结果(推荐)

xml
<resultMap id="OrderWithUserResult" type="Order">
    <id column="order_id" property="id"/>
    <result column="order_no" property="orderNo"/>
    <result column="total_amount" property="totalAmount"/>
    
    <!-- 一对一关联:嵌套结果 -->
    <association property="user" javaType="User">
        <id column="user_id" property="id"/>
        <result column="user_name" property="name"/>
        <result column="email" property="email"/>
    </association>
</resultMap>

<select id="selectOrderWithUser" resultMap="OrderWithUserResult">
    SELECT o.id as order_id,
           o.order_no,
           o.total_amount,
           u.id as user_id,
           u.user_name,
           u.email
    FROM orders o
    LEFT JOIN user u ON o.user_id = u.id
    WHERE o.id = #{id}
</select>

Java 调用:

java
Order order = session.selectOne("mapper.OrderMapper.selectOrderWithUser", 1L);
System.out.println(order.getUser().getName());

方式二:嵌套查询(N+1 问题)

xml
<!-- 查询订单 -->
<resultMap id="OrderWithUserResult2" type="Order">
    <id column="id" property="id"/>
    <result column="order_no" property="orderNo"/>
    <result column="total_amount" property="totalAmount"/>
    
    <!-- 嵌套查询:先查订单,再查用户 -->
    <association property="user" column="user_id" 
                 select="mapper.UserMapper.selectById"/>
</resultMap>

<select id="selectOrderWithUser2" resultMap="OrderWithUserResult2">
    SELECT id, order_no, total_amount, user_id
    FROM orders
    WHERE id = #{id}
</select>

<!-- 用户查询(被嵌套调用) -->
<select id="selectById" resultType="User">
    SELECT id, user_name as name, email
    FROM user
    WHERE id = #{id}
</select>

特点:

  • 先执行主查询(订单)
  • 再根据 column="user_id" 的值执行关联查询(用户)
  • 总共执行 2 次 SQL

方式三:注解方式

java
public interface OrderMapper {
    
    @Results({
        @Result(column = "id", property = "id"),
        @Result(column = "order_no", property = "orderNo"),
        @Result(column = "user_id", property = "userId"),
        @Result(column = "user_id", property = "user",
                one = @One(select = "com.example.mapper.UserMapper.selectById"))
    })
    @Select("SELECT * FROM orders WHERE id = #{id}")
    Order selectOrderWithUser(Long id);
}

一对多关联

场景:用户 → 订单(一个用户有多个订单)

实体类

java
public class User {
    private Long id;
    private String name;
    private List<Order> orders;  // 一对多关联
}

方式一:嵌套结果

xml
<resultMap id="UserWithOrdersResult" type="User">
    <id column="user_id" property="id"/>
    <result column="user_name" property="name"/>
    <result column="email" property="email"/>
    
    <!-- 一对多关联:嵌套结果 -->
    <collection property="orders" ofType="Order">
        <id column="order_id" property="id"/>
        <result column="order_no" property="orderNo"/>
        <result column="total_amount" property="totalAmount"/>
    </collection>
</resultMap>

<select id="selectUserWithOrders" resultMap="UserWithOrdersResult">
    SELECT u.id as user_id,
           u.user_name,
           u.email,
           o.id as order_id,
           o.order_no,
           o.total_amount
    FROM user u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE u.id = #{id}
</select>

方式二:嵌套查询

xml
<resultMap id="UserWithOrdersResult2" type="User">
    <id column="id" property="id"/>
    <result column="user_name" property="name"/>
    <result column="email" property="email"/>
    
    <!-- 嵌套查询 -->
    <collection property="orders" column="id"
               select="mapper.OrderMapper.selectByUserId"/>
</resultMap>

<select id="selectUserWithOrders2" resultMap="UserWithOrdersResult2">
    SELECT * FROM user WHERE id = #{id}
</select>

<!-- 订单查询(嵌套) -->
<select id="selectByUserId" resultType="Order">
    SELECT id, order_no, total_amount
    FROM orders
    WHERE user_id = #{userId}
    ORDER BY id
</select>

方式三:注解方式

java
public interface UserMapper {
    
    @Results({
        @Result(column = "id", property = "id"),
        @Result(column = "user_name", property = "name"),
        @Result(column = "orders", property = "orders",
                many = @Many(select = "com.example.mapper.OrderMapper.selectByUserId"))
    })
    @Select("SELECT * FROM user WHERE id = #{id}")
    User selectUserWithOrders(Long id);
}

多对多关联

场景:学生 ↔ 课程(学生可以选多门课,课程可以被多名学生选)

表结构

sql
-- 学生表
CREATE TABLE student (
    id BIGINT PRIMARY KEY,
    student_name VARCHAR(50)
);

-- 课程表
CREATE TABLE course (
    id BIGINT PRIMARY KEY,
    course_name VARCHAR(100)
);

-- 中间表(选课)
CREATE TABLE student_course (
    student_id BIGINT,
    course_id BIGINT,
    PRIMARY KEY (student_id, course_id),
    FOREIGN KEY (student_id) REFERENCES student(id),
    FOREIGN KEY (course_id) REFERENCES course(id)
);

实体类

java
public class Student {
    private Long id;
    private String name;
    private List<Course> courses;
}

public class Course {
    private Long id;
    private String name;
    private List<Student> students;
}

查询学生及其课程

xml
<resultMap id="StudentWithCoursesResult" type="Student">
    <id column="student_id" property="id"/>
    <result column="student_name" property="name"/>
    
    <collection property="courses" ofType="Course">
        <id column="course_id" property="id"/>
        <result column="course_name" property="name"/>
    </collection>
</resultMap>

<select id="selectStudentWithCourses" resultMap="StudentWithCoursesResult">
    SELECT s.id as student_id,
           s.student_name,
           c.id as course_id,
           c.course_name
    FROM student s
    LEFT JOIN student_course sc ON s.id = sc.student_id
    LEFT JOIN course c ON sc.course_id = c.id
    WHERE s.id = #{id}
</select>

查询课程及其学生

xml
<resultMap id="CourseWithStudentsResult" type="Course">
    <id column="course_id" property="id"/>
    <result column="course_name" property="name"/>
    
    <collection property="students" ofType="Student">
        <id column="student_id" property="id"/>
        <result column="student_name" property="name"/>
    </collection>
</resultMap>

<select id="selectCourseWithStudents" resultMap="CourseWithStudentsResult">
    SELECT c.id as course_id,
           c.course_name,
           s.id as student_id,
           s.student_name
    FROM course c
    LEFT JOIN student_course sc ON c.id = sc.course_id
    LEFT JOIN student s ON sc.student_id = s.id
    WHERE c.id = #{id}
</select>

延迟加载

配置开启

xml
<settings>
    <!-- 开启延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 积极加载改为按需加载 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

使用延迟加载

java
// 只有访问关联对象时才触发查询
Order order = session.selectOne("selectOrderWithUser2", 1L);
System.out.println(order.getOrderNo());  // 只查订单

User user = order.getUser();  // 此时才触发用户查询
System.out.println(user.getName());

局部延迟加载(按需)

xml
<!-- 全局禁用,但此查询启用 -->
<resultMap id="OrderWithUserEagerResult" type="Order">
    <id column="id" property="id"/>
    <association property="user" column="user_id" 
                 select="mapper.UserMapper.selectById"
                 fetchType="eager"/>
</resultMap>

<!-- 此查询禁用(默认延迟) -->
<resultMap id="OrderWithUserLazyResult" type="Order">
    <id column="id" property="id"/>
    <association property="user" column="user_id" 
                 select="mapper.UserMapper.selectById"
                 fetchType="lazy"/>
</resultMap>

常见问题

1. 列名重复导致映射错误

xml
<!-- 给列起别名,避免重复 -->
<select id="selectUserWithOrders" resultMap="UserWithOrdersResult">
    SELECT u.id as user_id,
           u.user_name,
           o.id as order_id,
           o.order_no
    FROM user u
    LEFT JOIN orders o ON u.id = o.user_id
</select>

2. N+1 查询问题

问题:嵌套查询会执行 1 + N 次 SQL(N=关联数据量)

解决方案

  • 数据量小:使用嵌套结果(一次 JOIN)
  • 数据量大:使用嵌套查询 + 延迟加载
  • 分页:先查主表分页,再查关联数据

3. 嵌套结果分页问题

xml
<!-- 嵌套结果不能直接 LIMIT -->
<!-- 需要先分页查主表,再 JOIN 关联表 -->
<select id="selectUserWithOrdersPage" resultMap="UserWithOrdersResult">
    SELECT * FROM (
        SELECT u.id as user_id, u.user_name, u.email
        FROM user u
        ORDER BY u.id
        LIMIT #{offset}, #{limit}
    ) t
    LEFT JOIN orders o ON t.user_id = o.user_id
</select>

相关页面

  • **Java\MyBatis\MyBatis-完全指南** - 返回总览
  • **Java\MyBatis\MyBatis-Mapper映射** - 基础映射
  • **Java\MyBatis\MyBatis-缓存机制** - 一对多缓存问题

#标签 #mybatis #association #collection #关联查询 #延迟加载 #嵌套查询

Knowledge4J — Java 知识库