mybatis collection(建议收藏)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

前言

在使用 MyBatis 进行数据库操作时,我们常会遇到 一对多关系 的映射需求。例如,一个用户可能有多个订单,一个部门包含多个员工。如何将这些复杂的关系高效地映射到 Java 对象中?这正是 mybatis collection 的核心作用。本文将通过循序渐进的方式,结合实际案例,帮助读者掌握这一功能。


一、基础概念:什么是 MyBatis Collection?

1.1 关系映射的痛点

在数据库中,表之间的关联关系通常通过 外键 实现。例如,用户表 userid 字段可能是订单表 order 的外键 user_id
当查询用户信息时,若需要同时获取其所有订单数据,直接通过 SQL 联合查询(如 JOIN)可能会导致重复数据。例如:

SELECT u.id, o.order_no 
FROM user u 
LEFT JOIN orders o ON u.id = o.user_id;

此时,如果用户有 3 个订单,查询结果会返回 3 条记录,每条记录中 user 的信息重复出现。如何将这些重复的数据合并为一个对象?

1.2 Collection 的作用

mybatis collection 是 MyBatis 提供的 嵌套结果映射 机制,专门用于处理 一对多(One-to-Many) 的关系。它通过以下方式实现:

  • 合并重复的主对象:将同一主对象(如用户)的多条从记录(如订单)合并为一个集合。
  • 自动填充嵌套对象:将从记录的数据自动映射到集合中的子对象(如 List<Order>)。

形象比喻:

想象快递包裹中的多个小包裹,collection 就像一个智能分拣系统,将属于同一个主包裹的多个小包裹自动归类到一个集合中。


二、Collection 配置详解

2.1 核心属性

在 MyBatis 的 resultMap 中,通过 <collection> 标签定义一对多关系。其关键属性如下:

属性名作用描述必填
column指定主对象字段,用于匹配从对象的外键。例如 user_id 对应 User.id
ofType从对象的类型(全限定类名或别名)
select关联查询的 SQL 方法(需指向另一个 Mapper 的查询方法)
fetchType指定加载策略(lazy 延迟加载 / eager 立即加载,默认 lazy

2.2 配置步骤

2.2.1 定义实体类

假设用户和订单的实体类如下:

// User.java
public class User {
    private Integer id;
    private String name;
    private List<Order> orders; // 嵌套集合
    // getters/setters
}

// Order.java
public class Order {
    private Integer id;
    private String orderNo;
    private Integer userId;
    // getters/setters
}

2.2.2 配置 ResultMap

在 MyBatis XML 文件中,通过 <collection> 标签关联 UserOrder

<!-- UserMapper.xml -->
<resultMap id="UserResultMap" type="User">
    <id property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <collection
        property="orders"
        column="user_id"         <!-- 外键字段 -->
        ofType="Order"           <!-- 集合元素类型 -->
        select="selectOrdersByUserId" <!-- 关联查询方法 -->
    />
</resultMap>

<!-- 关联查询的 SQL -->
<select id="selectOrdersByUserId" resultType="Order">
    SELECT * FROM orders WHERE user_id = #{userId}
</select>

2.2.3 调用查询方法

在 Mapper 接口中定义方法:

// UserMapper.java
public interface UserMapper {
    @Select("SELECT * FROM user WHERE id = #{id}")
    @ResultMap("UserResultMap")
    User selectUserWithOrders(Integer id);
}

三、实战案例:用户与订单的完整示例

3.1 数据库设计

假设存在以下两张表:

-- 用户表
CREATE TABLE user (
    id INT PRIMARY KEY,
    name VARCHAR(255)
);

-- 订单表
CREATE TABLE orders (
    id INT PRIMARY KEY,
    order_no VARCHAR(50),
    user_id INT,
    FOREIGN KEY (user_id) REFERENCES user(id)
);

3.2 完整配置与代码

3.2.1 实体类

// User.java
public class User {
    private Integer id;
    private String name;
    private List<Order> orders;
    // getters/setters
}

// Order.java
public class Order {
    private Integer id;
    private String orderNo;
    private Integer userId;
    // getters/setters
}

3.2.2 MyBatis XML 配置

<!-- UserMapper.xml -->
<resultMap id="UserResultMap" type="User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <collection
        property="orders"
        ofType="Order"
        column="id"          <!-- 主对象的 id 字段 -->
        select="selectOrders"
    />
</resultMap>

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

<select id="selectOrders" resultType="Order">
    SELECT * FROM orders WHERE user_id = #{userId}
</select>

3.2.3 测试代码

// 测试类
public class TestMyBatis {
    public static void main(String[] args) {
        SqlSessionFactory sqlSessionFactory = ... // 初始化工厂
        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.selectUserById(1);
            System.out.println("User Name: " + user.getName());
            for (Order order : user.getOrders()) {
                System.out.println("Order No: " + order.getOrderNo());
            }
        }
    }
}

四、常见问题与解决方案

4.1 错误 1:集合为空

现象:即使用户有订单,orders 集合仍为空。
原因select 属性指向的 SQL 方法未正确接收参数。
解决

  • 确保 column 属性的值与 SQL 参数名称匹配。例如,若 SQL 参数是 userId,则 column 应为 id(主对象的字段名)。
  • 检查 SQL 语句是否正确关联了外键字段。

4.2 错误 2:性能问题

现象:查询耗时显著增加。
原因:MyBatis 默认使用 延迟加载,可能触发大量小查询。
解决

  • 改用 嵌套查询JOIN 方式):
    <collection
        property="orders"
        ofType="Order"
        column="id"
        select="selectOrders"
        fetchType="eager"  <!-- 强制立即加载 -->
    />
    
  • 或直接使用联合查询,通过 columnPrefix 合并字段:
    <resultMap id="UserResultMap" type="User">
        <id property="id" column="user_id"/>
        <result property="name" column="user_name"/>
        <collection
            property="orders"
            ofType="Order"
            columnPrefix="order_"
        >
            <id property="id" column="id"/>
            <result property="orderNo" column="order_no"/>
        </collection>
    </resultMap>
    
    <select id="selectUserWithOrders" resultMap="UserResultMap">
        SELECT 
            u.id AS user_id,
            u.name AS user_name,
            o.id AS order_id,
            o.order_no AS order_order_no
        FROM user u
        LEFT JOIN orders o ON u.id = o.user_id
        WHERE u.id = #{id}
    </select>
    

五、进阶技巧

5.1 嵌套多级关联

collection 可以嵌套使用,例如用户 → 订单 → 商品:

<resultMap id="UserResultMap" type="User">
    ...
    <collection property="orders" ofType="Order">
        <id property="id" column="order_id"/>
        <result property="orderNo" column="order_no"/>
        <!-- 嵌套关联商品 -->
        <collection property="products" ofType="Product">
            <id property="id" column="product_id"/>
            <result property="name" column="product_name"/>
        </collection>
    </collection>
</resultMap>

5.2 动态 SQL 与 Collection 结合

在复杂查询中,可结合 <if> 等动态标签:

<collection property="orders" ofType="Order">
    <if test="_parameter != null">
        <select>
            SELECT * FROM orders 
            WHERE user_id = #{userId} 
            AND status = #{status}
        </select>
    </if>
</collection>

六、总结

通过本文的讲解,读者应能掌握以下核心内容:

  1. MyBatis Collection 的基本原理:解决一对多关系的重复数据合并问题。
  2. 配置方法:通过 <collection> 标签定义映射关系,结合 columnselect 属性。
  3. 实战案例:从数据库设计到代码实现的完整流程。
  4. 常见问题与性能优化:避免空集合、选择合适的加载策略。

掌握这一功能后,开发者可以更高效地处理复杂对象的持久化操作,提升代码的可维护性和可读性。后续可进一步探索 MyBatis 的其他关联映射功能(如 <association>),逐步构建更强大的数据访问层。

最新发布