一、<association>
标签
1. 复用性
独立
resultMap
复用
通过<association>
的resultMap
属性引用已定义的映射(如resultMap="addressMap"
),同一关联对象(如Address
)可在多个查询中复用映射规则,避免重复配置。示例:
<!-- 独立定义Address的映射 --> <resultMap id="addressMap" type="Address"> <id property="id" column="address_id"/> <result property="city" column="city"/> </resultMap> <!-- 在User中复用 --> <resultMap id="userMap" type="User"> <id property="id" column="user_id"/> <association property="address" resultMap="addressMap"/> </resultMap>
2. 结构清晰
- 分离主对象与关联对象映射
内联写法(将关联对象字段直接写在主resultMap
中)会导致配置臃肿,而<association>
将关联对象的映射逻辑封装在独立标签内,结构更清晰。
对比:<!-- ❌ 内联写法:混乱且无法复用 --> <resultMap id="userMap" type="User"> <id property="id" column="user_id"/> <result property="address.id" column="address_id"/> <!-- 内联关联对象字段 --> <result property="address.city" column="city"/> </resultMap> <!-- ✅ 标准association:逻辑分离 --> <resultMap id="userMap" type="User"> <id property="id" column="user_id"/> <association property="address" javaType="Address"> <id property="id" column="address_id"/> <result property="city" column="city"/> </association> </resultMap>
3. 支持复杂映射策略
- 灵活选择加载方式
<association>
支持两种模式:- 嵌套查询(Nested Select):通过
select
属性引用另一查询,支持懒加载(fetchType="lazy"
)。 - 嵌套结果(Nested Result):通过 JOIN 查询一次性加载所有数据,避免 N+1 问题。
内联写法仅支持嵌套结果模式,无法实现按需加载。
- 嵌套查询(Nested Select):通过
二、示例
1.实体类设计
// 地址实体
public class Address {
private Integer id; // 地址ID
private String city; // 城市
private String street; // 街道
// getter/setter
}
// 学生实体
public class Student {
private Integer id; // 学生ID
private String name; // 姓名
private Address address; // 一对一关联地址对象
// getter/setter
}
2.嵌套查询(Nested Select)
原理:分两次查询数据库,先查学生,再根据学生表的外键查地址。
适用场景:地址数据需懒加载、学生表数据量较大时避免一次性 JOIN 的性能压力。
<!-- 1. 独立地址查询 -->
<select id="findAddressById" resultType="Address">
SELECT id, city, street
FROM address
WHERE id = #{addressId} <!-- 接收student表传递的address_id -->
</select>
<!-- 2. 学生映射关联地址 -->
<resultMap id="studentMap" type="Student">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- 关键:association调用子查询 -->
<association
property="address"
column="address_id" <!-- 将student表的address_id传给子查询 -->
select="findAddressById" <!-- 指向子查询方法 -->
fetchType="lazy" <!-- 开启懒加载 -->
/>
</resultMap>
<!-- 3. 主查询:仅查学生表 -->
<select id="selectStudent" resultMap="studentMap">
SELECT id, name, address_id
FROM student
WHERE id = #{studentId}
</select>
执行流程:
- 执行
selectStudent
获取学生数据(如id=1, name="张三", address_id=101
)。 - 当调用
student.getAddress()
时,触发子查询findAddressById(101)
加载地址数据。
3.嵌套结果(Nested Result)
原理:单次 JOIN 查询,通过结果集映射直接封装关联对象。
适用场景:需一次性高效加载所有数据,避免多次查询的延迟问题。
<resultMap id="studentWithAddressMap" type="Student">
<!-- 学生字段映射 -->
<id property="id" column="student_id"/> <!-- 别名避免JOIN字段冲突 -->
<result property="name" column="student_name"/>
<!-- 关键:association直接映射地址对象 -->
<association property="address" javaType="Address">
<id property="id" column="address_id"/>
<result property="city" column="city"/>
<result property="street" column="street"/>
</association>
</resultMap>
<!-- 单次JOIN查询 -->
<select id="selectStudentWithAddress" resultMap="studentWithAddressMap">
SELECT
s.id AS student_id,
s.name AS student_name,
a.id AS address_id,
a.city,
a.street
FROM student s
LEFT JOIN address a ON s.address_id = a.id
WHERE s.id = #{studentId} <!-- 支持返回多个学生:去掉WHERE或改用IN -->
</select>
字段冲突处理:
JOIN 查询中必须为同名字段(如 id
)添加别名(如 s.id AS student_id
),否则 MyBatis 无法区分关联表字段。
4. 嵌套查询方案与嵌套结果方案对比
维度 | 嵌套查询(Nested Select) | 嵌套结果(Nested Result) |
---|---|---|
查询次数 | 2次(主查询 + 子查询) | ⚡️ 1次(单SQL完成) |
性能 | ❌ 可能N+1问题(主查询返回多行时) | ✅ 大数据量高效(无额外查询) |
延迟加载 | ✅ 支持(fetchType="lazy" ) |
❌ 强制立即加载 |
字段冲突处理 | ✅ 无需处理(子查询分离) | ⚠️ 需为JOIN字段添加别名 |
适用场景 | 地址数据独立性强、按需加载(如地址页非核心信息) | 地址需实时展示、学生列表页(高效JOIN) |
✅ 选型建议:
- 嵌套查询:地址数据非核心、需懒加载时使用;
- 嵌套结果:地址数据需高频实时展示时使用(推荐主流场景)。
5.总结
- 嵌套查询的 N+1 陷阱
主查询返回 N 个学生时,会触发 N 次地址子查询。需通过<association fetchType="lazy">
+ 全局配置lazyLoadingEnabled=true
规避。 - JOIN 字段别名强制规范
嵌套结果中若省略别名(如s.id AS student_id
),MyBatis 会因字段名冲突映射错误。 - 关联外键一致性
确保student.address_id
与address.id
类型严格匹配(如均为INT
),否则关联失效。