当前位置:首页 > 问答 > 正文

MyBatis里ResultMap那个Association和Collection标签到底怎么用,细节和区别都说清楚点

一对一用 association,一对多用 collection。

你的 Java 类之间有关联关系,比如一个订单(Order)属于一个用户(User),这是“一对一”(一个订单只对应一个用户),而一个用户(User)有多个订单(Order),这就是“一对多”,数据库里用外键表示这种关系,但查出来放到 Java 对象里,就需要 associationcollection 来帮忙。


association 标签:处理“有一个”的关系(一对一)

当你的主对象(Order)里面包含了一个单独的附属对象(User),就用 association

举个例子:订单和用户

  • 数据库表orders 表有一个 user_id 字段指向 user 表的主键。

  • Java 类

    MyBatis里ResultMap那个Association和Collection标签到底怎么用,细节和区别都说清楚点

    public class Order {
        private Integer id;
        private String orderNumber;
        // 这个订单属于哪个用户?是一个完整的 User 对象
        private User user; // 这就是“一对一”关系
        // ... getter and setter
    }
    public class User {
        private Integer id;
        private String username;
        // ... getter and setter
    }

目标:查询订单时,希望能直接把对应的用户信息也查出来,并填充到 Order 对象的 user 属性里。

怎么用 association?有两种主要方式:

嵌套结果映射(推荐,一次查询) 这种方式是写一个复杂的 SQL 联表查询,然后用 association 告诉 MyBatis 结果集的哪部分映射到 user 对象。

<select id="findOrderWithUser" resultMap="OrderUserResultMap">
    SELECT
        o.id as order_id,
        o.order_number,
        u.id as user_id,
        u.username
    FROM orders o
    LEFT JOIN user u ON o.user_id = u.id
    WHERE o.id = #{id}
</select>
<resultMap id="OrderUserResultMap" type="Order">
    <!-- 先映射 Order 本身的属性 -->
    <id property="id" column="order_id"/>
    <result property="orderNumber" column="order_number"/>
    <!-- 关键在这里:使用 association 映射关联的 User 对象 -->
    <association property="user" javaType="User">
        <!-- 在 association 标签内部,映射 User 对象的属性 -->
        <!-- 注意这里的 column 是上面 SELECT 语句里起的别名 -->
        <id property="id" column="user_id"/>
        <result property="username" column="username"/>
    </association>
</resultMap>
  • property="user":对应 Order 类里的 user 属性名。
  • javaType="User":告诉 MyBatis 这个属性对应的 Java 类型是什么。
  • 优点:只需要一次数据库查询,效率高。

嵌套查询(需要多次查询) 这种方式是先查主对象(Order),然后根据查到的外键(user_id)再去执行另一个查询(getUser)来获取关联对象(User)。

<select id="findOrderWithUser" resultMap="OrderUserResultMap">
    SELECT * FROM orders WHERE id = #{id}
</select>
<select id="getUser" resultType="User">
    SELECT * FROM user WHERE id = #{userId}
</select>
<resultMap id="OrderUserResultMap" type="Order">
    <id property="id" column="id"/>
    <result property="orderNumber" column="order_number"/>
    <!-- 关键在这里:通过 select 属性指定另一个查询的 id -->
    <!-- 通过 column 属性将当前结果中的 user_id 值传递给 getUser 查询 -->
    <association property="user" column="user_id" javaType="User" select="getUser"/>
</resultMap>
  • select="getUser":指定用来查询 User 的 SQL 映射语句的 id。
  • column="user_id":将本查询结果中的 user_id 列的值作为参数传递给 getUser 查询。
  • 缺点:可能会引发“N+1 查询问题”(如果查 10 个订单,就要执行 1 次查订单 + 10 次查用户,共11次查询),效率低,但可以通过 MyBatis 的懒加载等机制缓解。

collection 标签:处理“有多个”的关系(一对多)

当你的主对象(User)里面包含了一个集合(比如订单的 List),就用 collection

MyBatis里ResultMap那个Association和Collection标签到底怎么用,细节和区别都说清楚点

接着上面的例子:用户和订单

  • Java 类

    public class User {
        private Integer id;
        private String username;
        // 这个用户有哪些订单?是一个 Order 对象的集合
        private List<Order> orders; // 这就是“一对多”关系
        // ... getter and setter
    }
    public class Order {
        private Integer id;
        private String orderNumber;
        // ... getter and setter
    }

目标:查询用户时,希望能直接查出他所有的订单,并填充到 User 对象的 orders 属性(一个列表)里。

怎么用 collection?同样有两种方式:

嵌套结果映射(推荐,一次查询) 写一个联表查询,查出用户和他的所有订单数据。

MyBatis里ResultMap那个Association和Collection标签到底怎么用,细节和区别都说清楚点

<select id="findUserWithOrders" resultMap="UserOrdersResultMap">
    SELECT
        u.id as user_id,
        u.username,
        o.id as order_id,
        o.order_number
    FROM user u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE u.id = #{id}
</select>
<resultMap id="UserOrdersResultMap" type="User">
    <!-- 先映射 User 本身的属性 -->
    <id property="id" column="user_id"/>
    <result property="username" column="username"/>
    <!-- 关键在这里:使用 collection 映射关联的 Order 对象集合 -->
    <collection property="orders" ofType="Order">
        <!-- 在 collection 标签内部,映射集合中每个 Order 对象的属性 -->
        <id property="id" column="order_id"/>
        <result property="orderNumber" column="order_number"/>
    </collection>
</resultMap>
  • property="orders":对应 User 类里的 orders 属性名。
  • ofType="Order"这是和 association 最大的区别之一ofType 指定的是集合中的元素的 Java 类型(因为 orders 是 List<Order>,所以元素类型是 Order)。

嵌套查询(需要多次查询) 先查询用户,再根据用户 id 去查询订单列表。

<select id="findUserWithOrders" resultMap="UserOrdersResultMap">
    SELECT * FROM user WHERE id = #{id}
</select>
<select id="getOrdersByUserId" resultType="Order">
    SELECT * FROM orders WHERE user_id = #{userId}
</select>
<resultMap id="UserOrdersResultMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <!-- 关键在这里 -->
    <collection property="orders" column="id" ofType="Order" select="getOrdersByUserId"/>
</resultMap>
  • 逻辑和 association 的嵌套查询完全一样,只是标签换成了 collection,并且用 ofType 指定集合元素类型。

核心区别与细节总结

  1. 根本区别

    • association:用于映射单个对象属性(一对一,多对一)。Order 里面有个 User
    • collection:用于映射集合对象属性(一对多)。User 里面有个 List<Order>
  2. 标签属性

    • associationjavaType 指定关联对象的类型。
    • collectionofType 指定集合中元素的类型,这是最容易记混的点。
  3. 性能选择

    • 嵌套结果映射(写联表SQL):绝大多数场景推荐这个,一次SQL查询搞定,效率高。
    • 嵌套查询(分两次查):只有在关联数据很大、不常用(可以用懒加载)、或者SQL过于复杂时才考虑,要警惕N+1查询问题。
  4. 列名冲突:在写联表查询的 SQL 时,两个表可能有同名的列(比如都有 id)。务必使用别名(AS) 来区分,并在 resultMapcolumn 属性中使用别名,这是新手常踩的坑。

你只要搞清楚你的 Java 对象是“有一个”还是“有多个”其他对象,就能决定用 association 还是 collection 了,然后优先选择写联表 SQL 配合嵌套结果映射的方式,这样最直观也最高效。