MyBatis 通过 <collection> 标签配合 resultMap 实现一对多和多对多映射,推荐嵌套结果(JOIN)方式,需配置 <id> 避免重复;嵌套查询适用于复杂场景但需开启延迟加载,且必须指定 ofType 和 property。

MyBatis 处理一对多和多对多的嵌套集合,核心靠 <collection> 标签配合 resultMap 实现。关键不在于“能不能”,而在于选对方式——是用单次 JOIN 查询组装(嵌套结果),还是分步查(嵌套查询)。两种方式逻辑清晰、适用场景明确,选错容易引发 N+1 问题或字段冲突。
一对多:用 <collection> 映射 List 或 Set 属性
典型场景:一个部门(Dept)对应多个员工(Employee),Dept 类中声明 private List<Employee> employees;。
- 嵌套结果(推荐多数情况):写一条 LEFT JOIN SQL,把 Dept 和 Employee 字段全查出来,在 resultMap 里用 <collection> 指定如何从结果集中提取并封装 Employee 对象
- 嵌套查询(适合复杂过滤或复用子查询):主查询只查 Dept,再通过 <collection select="xxx"> 调用另一个 SQL,传入 dept.id 去查其下属员工
- 必须指定
ofType="Employee",告诉 MyBatis 集合里装的是什么类型;property="employees"要和 Java 属性名严格一致
多对多:本质是一对多 + 中间表,仍用 <collection>
例如:用户(User)与角色(Role)是多对多,中间表 user_role。实际映射时,User 类含 private List<Role> roles;,并不需要专门建 RoleUser 关联实体。
- 用嵌套结果:SQL 写三表 JOIN(user、user_role、role),在 User 的 resultMap 中,<collection> 直接映射 role 表字段到 Role 对象属性
- 用嵌套查询:先查用户,再调用
selectRolesByUserId查询其所有角色;注意该子查询返回的是 Role 列表,不是中间表数据 - 字段别名要区分清楚,比如 role.id → rid,role.name → rname,避免和 user 表字段重名导致覆盖
避坑要点:别让集合为空或重复
嵌套结果下,JOIN 可能导致主表记录被“撑开”(一行 Dept 变成多行),MyBatis 依赖 <id> 标签标识主对象唯一性 来自动去重合并。所以:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
立即学习“Java免费学习笔记(深入)”;
- 主对象 resultMap 必须包含 <id>(如 <id property="id" column="dept_id"/>),否则集合会错乱或丢失
- collection 内部也建议配 <id>(如 <id property="id" column="emp_id"/>),提升嵌套对象识别稳定性
- 若用嵌套查询,记得开启延迟加载(
lazyLoadingEnabled=true),否则一查用户就全量拉角色,性能雪崩
简单示例:嵌套结果实现用户+角色
SQL:SELECT u.id uid, u.name uname, r.id rid, r.name rname FROM user u LEFT JOIN user_role ur ON u.id = ur.user_id LEFT JOIN role r ON ur.role_id = r.id WHERE u.id = #{id}
resultMap:
<resultMap id="userWithRoles" type="User"><id property="id" column="uid"/>
<result property="name" column="uname"/>
<collection property="roles" ofType="Role">
<id property="id" column="rid"/>
<result property="name" column="rname"/>
</collection>
</resultMap>

















