多对多关联查询
多对多关系通过中间表(关联表)实现,MyBatis 使用 collection 嵌套映射将关联数据组装为对象集合。
典型场景:学生与课程
SQL
-- 学生表
CREATE TABLE student (id INT PRIMARY KEY, name VARCHAR(50));
-- 课程表
CREATE TABLE course (id INT PRIMARY KEY, name VARCHAR(50));
-- 中间表
CREATE TABLE student_course (
student_id INT,
course_id INT,
PRIMARY KEY (student_id, course_id)
);
Java
public class Student {
private Integer id;
private String name;
private List<Course> courses;
}
public class Course {
private Integer id;
private String name;
private List<Student> students;
}
Join 方式(推荐)
XML
<resultMap id="studentWithCourses" type="Student">
<id property="id" column="s_id"/>
<result property="name" column="s_name"/>
<collection property="courses" ofType="Course">
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
</collection>
</resultMap>
<select id="selectStudentWithCourses" resultMap="studentWithCourses">
SELECT s.id s_id, s.name s_name,
c.id c_id, c.name c_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="studentResultMap" type="Student">
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="courses"
column="id"
ofType="Course"
select="selectCoursesByStudent"/>
</resultMap>
<select id="selectStudent" resultMap="studentResultMap">
SELECT * FROM student WHERE id = #{id}
</select>
<select id="selectCoursesByStudent" resultType="Course">
SELECT c.id, c.name
FROM course c
INNER JOIN student_course sc ON c.id = sc.course_id
WHERE sc.student_id = #{studentId}
</select>
双向关联(级联加载)
同时查询学生的课程和课程下的学生:
XML
<resultMap id="courseWithStudents" type="Course">
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
<collection property="students" ofType="Student">
<id property="id" column="s_id"/>
<result property="name" column="s_name"/>
</collection>
</resultMap>
<select id="selectCourseWithStudents" resultMap="courseWithStudents">
SELECT c.id c_id, c.name c_name,
s.id s_id, s.name s_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
<resultMap id="userWithOrders" type="User">
<id property="id" column="u_id"/>
<result property="name" column="u_name"/>
<collection property="orders" ofType="Order">
<id property="id" column="o_id"/>
<result property="date" column="o_date"/>
<collection property="products" ofType="Product">
<id property="id" column="p_id"/>
<result property="name" column="p_name"/>
<result property="price" column="p_price"/>
</collection>
</collection>
</resultMap>
<select id="selectUserWithOrders" resultMap="userWithOrders">
SELECT u.id u_id, u.name u_name,
o.id o_id, o.date o_date,
p.id p_id, p.name p_name, p.price p_price
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
LEFT JOIN order_item oi ON o.id = oi.order_id
LEFT JOIN products p ON oi.product_id = p.id
WHERE u.id = #{id}
</select>
多层嵌套时,中间表的 JOIN 顺序会影响数据去重,需确保关联键唯一。
Join vs Subquery 对比
| 维度 | Join 方式 | 子查询方式 |
|---|---|---|
| SQL 数量 | 1 条 | 至少 N+1 条 |
| 笛卡尔积风险 | 多对多易产生膨胀 | 无 |
| 性能 | 数据量小时更优 | 数据量大时更可控 |
| 延迟加载 | 不支持 | 支持 |
| 适用场景 | 数据量可控 | 数据量大或按需加载 |
多对多关系使用 Join 时,若两端数据量都较大,会产生笛卡尔积膨胀,此时应使用子查询方式。
要点总结
- 多对多关系通过中间表 + collection 嵌套实现。
- Join 方式适合数据量可控场景,一次查询完成。
- 子查询方式支持延迟加载,避免笛卡尔积膨胀。
- 双向关联需在两端分别配置 collection。
- 多层嵌套建议不超过三层,否则性能明显下降。
- 数据量大时优先使用子查询方式。
📝 发现内容有误?点击此处直接编辑