MyBatis的级联查询
级联的优点是获取关联数据十分便捷。但是级联过多会增加系统的复杂度,同时降低系统的性能,此增彼减。所以记录超过 3 层时,就不要考虑使用级联了,因为这样会造成多个对象的关联,导致系统的耦合、负载和难以维护。
标签说明:
- property:指定映射到实体类的对象属性。
- column:指定表中对应的字段(即查询返回的列名)。
- javaType:指定映射到实体对象属性的类型。
- select:指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询。
MyBatis的级联查询(一对一、一对多、多对多)主要通过ResultMap的<association>
和<collection>
标签实现。
1.一对一关系
场景:如订单(Order)与用户(User)、身份证与公民,一个订单仅对应一个用户。
1. 建表原则
- 主键对应:两表主键一致,直接通过主键关联。
- 外键唯一约束:在任意一方添加外键并设置唯一约束(如
user_id
字段)
2. 实现方式
-
分步查询:分两次查询,先查主表,再通过外键查询关联表。
<resultMap id="OrderResult" type="Order"><id property="id" column="id"/><association property="user" column="user_id" select="findUserById"/> </resultMap>
需定义
findUserById
的查询语句 -
嵌套结果映射:单次联表查询,通过
<association>
映射关联对象。<resultMap id="OrderWithUserResult" type="Order"><id property="id" column="id"/><association property="user" javaType="User"><id property="id" column="user_id"/><result property="username" column="username"/></association> </resultMap>
SQL需使用
JOIN
联表查询
2.一对多关系
场景:如班级(Clazz)与学生(Student),一个班级对应多个学生。
1. 建表原则
- 在多的一方(学生表)添加外键指向一的一方(班级表主键)
2. 实现方式
-
嵌套查询:分两次查询,先查主表,再通过外键查关联表集合。
<resultMap id="ClazzResult" type="Clazz"><collection property="students" column="id" select="findStudentsByClazzId"/> </resultMap>
-
嵌套结果映射:单次联表查询,通过
<collection>
映射集合。<resultMap id="ClazzWithStudentsResult" type="Clazz"><id property="id" column="id"/><collection property="students" ofType="Student"><id property="id" column="student_id"/><result property="name" column="student_name"/></collection> </resultMap>
SQL需使用
LEFT JOIN
并关联外键
3.多对多关系
场景:如用户(User)与角色(Role),一个用户可拥有多个角色,一个角色可分配给多个用户。
1. 建表原则
- 通过中间表关联两表主键(如
user_role
表)
2. 实现方式
-
嵌套结果映射:通过两次
JOIN
联表查询,将中间表与主表关联。<resultMap id="RoleWithUsersResult" type="Role"><id property="roleId" column="rid"/><collection property="users" ofType="User"><id property="id" column="user_id"/><result property="username" column="username"/></collection> </resultMap>
SQL示例:
SQLSELECT r.*, u.* FROM role r LEFT JOIN user_role ur ON r.id = ur.rid LEFT JOIN user u ON ur.uid = u.id
4.核心配置标签对比
关系类型 | 标签 | 用途 | 关联对象类型 |
---|---|---|---|
一对一 | <association> | 映射单个关联对象 | javaType |
一对多 | <collection> | 映射对象集合 | ofType |
多对多 | <collection> | 通过中间表映射双向一对多关系 | ofType + 联表查询 |
5.优化建议
- 延迟加载:在嵌套查询中启用
lazyLoadingEnabled
,避免一次性加载所有关联数据。 - 动态SQL:通过
<if>
、<foreach>
等标签处理复杂条件 - 联表查询 vs 嵌套查询:联表查询减少数据库交互次数,但可能返回冗余数据;嵌套查询更灵活但可能引发N+1问题
6.一对多和多对一的使用异同总结
特性 | 一对多 | 多对一 |
---|---|---|
数据库关系 | 主表(一)的一条记录对应从表(多)的多条记录 | 从表(多)的外键指向主表(一)的主键 |
映射配置 | 在“一”端使用 <collection> 标签映射集合属性 | 在“多”端使用 <many-to-one> 标签映射单对象属性 |
外键位置 | 外键位于“多”的表,指向“一”的主键 | 与一对多相同,但视角相反(从“多”向“一”看) |
查询方向 | 通常从“一”端查询关联的“多”端数据(如班级查询学生) | 通常从“多”端查询关联的“一”端数据(如学生查询班级) |
数据维护 | 插入数据时需先维护“一”端,删除时需先删除“多”端 | 操作顺序与一对多一致,但逻辑上从“多”端操作 |
应用场景 | 适合主表与从表的层级关系(如部门与员工) | 适合从表需要频繁引用主表的场景(如订单与客户) |
关键区别
- 视角差异:一对多是从“一”端指向“多”端,而多对一是从“多”端指向“一”端,本质是同一关系的双向表达
- 映射标签:在ORM框架(如Hibernate)中,一对多使用
<collection>
多对一使用<many-to-one>
- 数据操作逻辑:一对多的查询可能涉及集合遍历(如班级所有学生),而多对一更关注单对象关联(如学生所属班级)
7.使用多对多时的核心要点
-
中间表设计
- 必须通过中间表(如
Student_Courses
)维护多对多关系,存储两表主键作为外键 - 若需记录额外属性(如选课时间),需扩展中间表为独立实体(如
Enrollment
)
- 必须通过中间表(如
-
查询优化
- 使用
JOIN
连接三张表,避免多次单表查询导致的性能问题 - 分页处理大数据量时,优先在中间表或主表过滤以减少笛卡尔积爆炸
- 使用
-
结果映射
- 在ORM框架中通过
<collection>
标签嵌套映射关联数据,如学生对象包含课程集合 - 双向关联时需避免循环引用(如学生引用课程,课程又引用学生),可通过DTO解耦或注解忽略
- 在ORM框架中通过
-
数据一致性
-
插入时需先操作主表(学生、课程),再操作中间表;删除时需先解除中间表关联
-
使用事务保证关联操作的原子性,避免部分失败导致脏数据
-
-
性能与冗余
- 添加索引优化中间表的外键字段,加速查询
- 避免在中间表中存储非关联数据,保持表的轻量化