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> 帮助对象去重与集合聚合。