在 MyBatis 中处理一对一关联时,使用 <association> 标签

发布于:2025-06-19 ⋅ 阅读:(18) ⋅ 点赞:(0)

一、<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 问题。
      内联写法仅支持嵌套结果模式,无法实现按需加载。

二、示例

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>

执行流程

  1. 执行 selectStudent 获取学生数据(如 id=1, name="张三", address_id=101)。
  2. 当调用 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.总结
  1. 嵌套查询的 N+1 陷阱
    主查询返回 N 个学生时,会触发 N 次地址子查询。需通过 <association fetchType="lazy"> + 全局配置 lazyLoadingEnabled=true 规避。
  2. JOIN 字段别名强制规范
    嵌套结果中若省略别名(如 s.id AS student_id),MyBatis 会因字段名冲突映射错误。
  3. 关联外键一致性
    确保 student.address_idaddress.id 类型严格匹配(如均为 INT),否则关联失效。

网站公告

今日签到

点亮在社区的每一天
去签到