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 关系映射的痛点
在数据库中,表之间的关联关系通常通过 外键 实现。例如,用户表 user
的 id
字段可能是订单表 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>
标签关联 User
和 Order
:
<!-- 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>
六、总结
通过本文的讲解,读者应能掌握以下核心内容:
- MyBatis Collection 的基本原理:解决一对多关系的重复数据合并问题。
- 配置方法:通过
<collection>
标签定义映射关系,结合column
和select
属性。 - 实战案例:从数据库设计到代码实现的完整流程。
- 常见问题与性能优化:避免空集合、选择合适的加载策略。
掌握这一功能后,开发者可以更高效地处理复杂对象的持久化操作,提升代码的可维护性和可读性。后续可进一步探索 MyBatis 的其他关联映射功能(如 <association>
),逐步构建更强大的数据访问层。