MyBatis 高级映射(association / collection)

一张表映射成简单对象不够用:博客有作者(对象属性)、作者有多篇文章(集合属性)。MyBatis 用 <association> 映射单个对象、<collection> 映射集合,两者统称高级映射,也是 resultMap 的进阶用法。

两种实现策略

把关联对象查出来有两条路,先记住结论再写代码:

策略原理优点缺点
嵌套查询(nested select)主查询每行再发一条子查询简单直观、可复用可能 N+1 次查询
嵌套结果(nested result)一条 JOIN SQL 一次取回查询次数少SQL 与映射复杂

association:一对一 / 多对一

实体里是"对象属性"(Blog 有一个 User 类型的 author)时用 association。 写法一:嵌套查询——把外层行的 author_id 当参数,去执行另一条语句:

<resultMap id="blogMap" type="blog">
    <id property="id" column="id"/>
    <result property="title" column="title"/>
    <association property="author" javaType="user"
                 column="author_id"
                 select="com.example.mapper.UserMapper.selectById"/>
</resultMap>
<select id="selectBlogById" resultMap="blogMap">
    SELECT id, title, author_id FROM blog WHERE id = #{id}
</select>

执行时先查 blog 得 author_id,再查用户。外层查回 100 篇博客就可能多出 100 次用户查询——这就是 N+1 问题写法二:嵌套结果——一条 SQL 用 JOIN 直接查出作者列,association 内部逐列映射:

<resultMap id="blogMap" type="blog">
    <id property="id" column="id"/>
    <result property="title" column="title"/>
    <association property="author" javaType="user">
        <id property="id" column="author_id"/>
        <result property="userName" column="user_name"/>
    </association>
</resultMap>
<select id="selectBlogById" resultMap="blogMap">
    SELECT b.id, b.title, u.id AS author_id, u.user_name
    FROM blog b JOIN user u ON u.id = b.author_id WHERE b.id = #{id}
</select>

collection:一对多

实体里是"集合属性"(User 有 List<Order> 类型的 orders)时用 collection,多一个 ofType 表示元素类型。 嵌套查询写法:

<resultMap id="userMap" type="user">
    <id property="id" column="id"/>
    <collection property="orders" ofType="order"
                column="id"
                select="com.example.mapper.OrderMapper.selectByUserId"/>
</resultMap>

嵌套结果写法:一条 LEFT JOIN 把主表与子表一次查出,collection 内部用 <id> 判断"哪些行属于同一个父对象",自动聚合成集合:

<resultMap id="userMap" type="user">
    <id property="id" column="id"/>
    <collection property="orders" ofType="order">
        <id property="id" column="order_id"/> <!-- 区分父对象,兼做去重 -->
        <result property="amount" column="amount"/>
    </collection>
</resultMap>
<select id="selectUserWithOrders" resultMap="userMap">
    SELECT u.id, u.user_name, o.id AS order_id, o.amount
    FROM user u LEFT JOIN orders o ON o.user_id = u.id
    WHERE u.id = #{id}
</select>

子查询返回的多行会被合并进同一个 user.orders;多表列名可能冲突,务必用 SQL 别名(author_id、order_id)区分。

用延迟加载缓解 N+1

嵌套查询可配合懒加载:关联对象只在真正被访问时才发起第二次查询。打开全局开关或给关联加 fetchType:

<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>
<association property="author" fetchType="lazy"
             column="author_id" select="...UserMapper.selectById"/>

懒加载的触发时机、aggressiveLazyLoading 等细节以官方文档为准。列表页等高频场景,优先用一条 JOIN 的嵌套结果,不要放任 N+1。

小结

对象属性用 association、集合属性用 collection;简单场景嵌套查询配懒加载/缓存,性能敏感场景用嵌套结果一条 JOIN,并用 <id> 帮助对象去重与集合聚合。

笔记加载中…