Mybatis+mysql 一对多查询问题

MyBatis + MySQL 实现 ResultMap + Collection 一对多查询

ResultMap + collection 是 MyBatis 中一对多关联查询的核心用法(比如:1 个用户对应多个订单、1 个班级对应多个学生)。

核心场景:主表一条数据,关联从表多条数据 ,用 collection 标签将从表的多条数据封装成集合,存入主表实体类中。

本文以 用户表 (user) ↔ 订单表 (order) 经典一对多场景为例,完整演示用法。


一、准备工作

1. 新建 MySQL 表

复制代码
-- 用户表(主表:一的一方)
CREATE TABLE user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(20) NOT NULL,
    age INT
);

-- 订单表(从表:多的一方)
CREATE TABLE `order` (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_no VARCHAR(32) NOT NULL, -- 订单编号
    user_id BIGINT NOT NULL,       -- 关联用户ID
    total_price DECIMAL(10,2),
    FOREIGN KEY (user_id) REFERENCES user(id)
);

-- 插入测试数据
INSERT INTO user(username, age) VALUES ('张三', 20), ('李四', 25);
INSERT INTO `order`(order_no, user_id, total_price) VALUES 
('ORDER_001', 1, 99.99),
('ORDER_002', 1, 199.99),
('ORDER_003', 2, 299.99);

2. 实体类

1. 订单实体(从表)
复制代码
import lombok.Data;
import java.math.BigDecimal;

@Data // Lombok 自动生成get/set/toString
public class Order {
    private Long id;
    private String orderNo;   // 订单编号
    private Long userId;      // 关联用户ID
    private BigDecimal totalPrice; // 订单总价
}
2. 用户实体(主表,包含订单集合)
复制代码
import lombok.Data;
import java.util.List;

@Data
public class User {
    private Long id;
    private String username;
    private Integer age;
    
    // 一对多:一个用户对应多个订单
    private List<Order> orderList;
}

二、核心实现:嵌套结果映射(推荐 ✅)

最优方案 :MySQL 联表查询(LEFT JOIN)+ MyBatis 一次性映射结果,无 N+1 查询问题,性能最好。

1. UserMapper 接口

复制代码
import org.apache.ibatis.annotations.Param;
import java.util.List;

public interface UserMapper {
    // 查询所有用户 + 每个用户的订单列表
    List<User> selectAllUserWithOrders();

    // 根据用户ID查询用户 + 订单列表
    User selectUserWithOrdersById(@Param("userId") Long userId);
}

2. UserMapper.xml 核心配置(重点)

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">

    <!-- 1. 定义订单的 ResultMap(复用) -->
    <resultMap id="OrderResultMap" type="com.example.entity.Order">
        <id column="order_id" property="id"/>
        <result column="order_no" property="orderNo"/>
        <result column="user_id" property="userId"/>
        <result column="total_price" property="totalPrice"/>
    </resultMap>

    <!-- 2. 核心:用户 ResultMap + collection 一对多映射 -->
    <resultMap id="UserWithOrderResultMap" type="com.example.entity.User">
        <!-- 主表:用户字段映射 -->
        <id column="user_id" property="id"/> <!-- 必须写id!MyBatis靠主键去重 -->
        <result column="username" property="username"/>
        <result column="age" property="age"/>

        <!-- 一对多:collection 标签封装订单集合
             property:User实体中 订单集合的属性名
             ofType:集合泛型的 全类名
             resultMap:引用上方定义的订单ResultMap
        -->
        <collection 
            property="orderList" 
            ofType="com.example.entity.Order"
            resultMap="OrderResultMap"/>
    </resultMap>

    <!-- 3. 联表查询SQL:LEFT JOIN 保证无订单的用户也能查到 -->
    <select id="selectAllUserWithOrders" resultMap="UserWithOrderResultMap">
        SELECT 
            u.id AS user_id,
            u.username,
            u.age,
            o.id AS order_id,
            o.order_no,
            o.user_id,
            o.total_price
        FROM user u
        LEFT JOIN `order` o ON u.id = o.user_id
    </select>

    <!-- 根据ID查询 -->
    <select id="selectUserWithOrdersById" resultMap="UserWithOrderResultMap">
        SELECT 
            u.id AS user_id,
            u.username,
            u.age,
            o.id AS order_id,
            o.order_no,
            o.user_id,
            o.total_price
        FROM user u
        LEFT JOIN `order` o ON u.id = o.user_id
        WHERE u.id = #{userId}
    </select>

</mapper>

还可以这么简写

我直接把非必须的映射全部删掉,这是最精简的可用版本:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">

    <resultMap id="UserWithOrderResultMap" type="com.example.entity.User">
        <!-- 🔴 必写:用户主键(唯一必须映射的主表字段) -->
        <id column="user_id" property="id"/>
        
        <!-- 👇 普通字段:想映射就写,不想写直接删! -->
        <result column="username" property="username"/>
        <!-- <result column="age" property="age"/>  不需要就注释/删除 -->


        <!-- 🔴 必写:集合 + 订单主键(唯一必须映射的从表字段) -->
        <collection property="orderList" ofType="com.example.entity.Order">
            <id column="order_id" property="id"/>
            
            <!-- 👇 普通字段:想映射就写,不想写直接删! -->
            <!-- <result column="order_no" property="orderNo"/> -->
            <!-- <result column="total_price" property="totalPrice"/> -->
        </collection>
    </resultMap>

    <!-- SQL 不变 -->
    <select id="selectAllUserWithOrders" resultMap="UserWithOrderResultMap">
        SELECT
            u.id AS user_id,
            u.username,
            u.age,
            o.id AS order_id,
            o.order_no,
            o.total_price
        FROM user u
        LEFT JOIN `order` o ON u.id = o.user_id
    </select>

</mapper>

三、补充:嵌套查询(不推荐 ❌)

原理:先查主表,再循环查从表 ,会产生 N+1 性能问题(1 条主表 SQL + N 条从表 SQL),仅作了解。

修改 UserMapper.xml

复制代码
<!-- 嵌套查询:先查用户,再查订单 -->
<resultMap id="UserWithOrderSelectMap" type="com.example.entity.User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="age" property="age"/>
    <!-- collection:select指定从表查询方法,column指定传递的参数 -->
    <collection
        property="orderList"
        ofType="com.example.entity.Order"
        select="com.example.mapper.OrderMapper.selectOrderByUserId"
        column="id"/>
</resultMap>

<select id="selectUserWithOrdersById" resultMap="UserWithOrderSelectMap">
    SELECT id, username, age FROM user WHERE id = #{userId}
</select>

<!-- OrderMapper.xml -->
<select id="selectOrderByUserId" resultType="com.example.entity.Order">
    SELECT * FROM `order` WHERE user_id = #{userId}
</select>
相关推荐
一定要AK2 小时前
MyBatis 从入门到精通
mybatis
黑牛儿3 小时前
MySQL负载均衡配置详细步骤(新手易操作版)
mysql·adb·负载均衡
kcuwu.3 小时前
从Python\+MySQL到Redis:非关系型数据库详解(PyCharm实操版)
redis·python·mysql
工具罗某人3 小时前
docker compose 部署MySQL InnoDB Cluster + Router 高可用集群-亲测可用
mysql·docker·容器
猿小喵4 小时前
MySQL长时间未提交事务分析
数据库·mysql·性能优化
江不清丶4 小时前
深入剖析 MySQL 日志系统:Redo Log、Undo Log 与 Binlog 的协同工作原理
数据库·mysql·adb
光泽雨4 小时前
mysql中的事务
数据库·mysql
黑牛儿4 小时前
MySQL 备份与恢复详细步骤(新手版)
数据库·mysql·dba
fundoit4 小时前
MySQL问题收集
数据库·人工智能·mysql·智能体