MyBatis 分步查询(含超级分步)笔记

分步查询是 MyBatis 处理关联关系(一对一、一对多) 的高效方式,核心是将复杂关联查询拆分为多次独立单表查询,按需加载数据,支持延迟加载,同时降低代码耦合度。

一、核心标签与属性

分步查询通过 resultMap 中的关联标签实现,核心属性如下:

标签 作用场景 核心属性
association 一对一关联 select:下一步查询的 Mapper 方法全路径

javaType:关联对象的类型

column:传递给下一步的参数列
collection 一对多关联 select:下一步查询的 Mapper 方法全路径

ofType:集合中元素的类型

column:传递给下一步的参数列
  • select:必须指定「命名空间 + 方法名」,确保能找到目标查询方法
  • column:支持单个参数(column="字段名")或多个参数(column="{paramKey=字段名, ...}"

二、基础分步查询实现

1. 一对一分步查询(association

场景:查询订单(Order)时,分步加载关联的客户(Customer)信息

相关表结构

  • t_order:id、address、amount、customer_id(关联 t_customer 的 id)
  • t_customer:id、customer_name、phone

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<mapper namespace="com.example.mybatis.mapper.OrderCustomerStepMapper">
<!-- 步骤1:独立查询客户(被分步调用的基础方法) -->
<select id="getCustomerById" resultType="com.example.mybatis.bean.Customer">
select * from t_customer where id = #{id}
</select>

<!-- 步骤2:自定义订单结果集,通过 association 分步关联客户 -->
<resultMap id="OrderCustomerStepRM" type="com.example.mybatis.bean.Order">
<id column="id" property="id"/>
<result column="address" property="address"/>
<result column="amount" property="amount"/>
<result column="customer_id" property="customerId"/>

<!-- 一对一分步查询:调用 getCustomerById 方法,传递 customer_id 作为参数 -->
<association property="customer" <!-- Order 类中关联客户的属性名 -->
javaType="com.example.mybatis.bean.Customer" <!-- 关联对象类型 -->
select="com.example.mybatis.mapper.OrderCustomerStepMapper.getCustomerById" <!-- 下一步查询方法 -->
column="customer_id"/> <!-- 传递当前查询的 customer_id 字段值 -->
</resultMap>

<!-- 步骤3:查询订单的入口方法(触发分步查询) -->
<select id="getOrderByIdAndCustomerStep" resultMap="OrderCustomerStepRM">
select * from t_order where id = #{id}
</select>
</mapper>

2. 一对多分步查询(collection

场景:查询客户(Customer)时,分步加载其关联的所有订单(Order)信息

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<mapper namespace="com.example.mybatis.mapper.OrderCustomerStepMapper">
<!-- 步骤1:独立查询客户的所有订单(被分步调用的基础方法) -->
<select id="getOrdersByCustomerId" resultType="com.example.mybatis.bean.Order">
select * from t_order where customer_id = #{cId} <!-- 参数 cId 对应 column 传递的值 -->
</select>

<!-- 步骤2:自定义客户结果集,通过 collection 分步关联订单 -->
<resultMap id="CustomerOrdersStepRM" type="com.example.mybatis.bean.Customer">
<id column="id" property="id"/>
<result column="customer_name" property="customerName"/>
<result column="phone" property="phone"/>

<!-- 一对多分步查询:调用 getOrdersByCustomerId 方法,传递 id 作为参数 -->
<collection property="orders" <!-- Customer 类中关联订单集合的属性名 -->
ofType="com.example.mybatis.bean.Order" <!-- 集合中元素类型 -->
select="com.example.mybatis.mapper.OrderCustomerStepMapper.getOrdersByCustomerId" <!-- 下一步查询方法 -->
column="id"/> <!-- 传递当前查询的客户 id 字段值 -->
</resultMap>

<!-- 步骤3:查询客户的入口方法(触发分步查询) -->
<select id="getCustomerByIdAndOrdersStep" resultMap="CustomerOrdersStepRM">
select * from t_customer where id = #{id}
</select>
</mapper>

三、超级分步查询(多层关联)

场景:查询订单 → 分步查询客户 → 再分步查询该客户的所有订单(三层关联:订单 → 客户 → 客户的所有订单)

核心逻辑:在第一层分步查询的关联标签(association/collection)中,嵌套另一个 resultMap,触发第二层分步查询。

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<mapper namespace="com.example.mybatis.mapper.OrderCustomerStepMapper">
<!-- 基础方法:查询客户(被第一层分步调用) -->
<select id="getCustomerById" resultType="com.example.mybatis.bean.Customer">
select * from t_customer where id = #{id}
</select>

<!-- 基础方法:查询客户的所有订单(被第二层分步调用) -->
<select id="getOrdersByCustomerId" resultType="com.example.mybatis.bean.Order">
select * from t_order where customer_id = #{cId}
</select>

<!-- 第二层 resultMap:客户 + 客户的订单(供超级分步查询嵌套使用) -->
<resultMap id="CustomerWithOrdersRM" type="com.example.mybatis.bean.Customer">
<id column="id" property="id"/>
<result column="customer_name" property="customerName"/>
<result column="phone" property="phone"/>

<!-- 客户关联订单:第二层分步查询 -->
<collection property="orders"
ofType="com.example.mybatis.bean.Order"
select="com.example.mybatis.mapper.OrderCustomerStepMapper.getOrdersByCustomerId"
column="id"/>
</resultMap>

<!-- 第一层 resultMap:订单 + 客户 + 客户的订单(超级分步查询核心) -->
<resultMap id="OrderCustomerWithOrdersSuperStepRM" type="com.example.mybatis.bean.Order">
<id column="id" property="id"/>
<result column="address" property="address"/>
<result column="amount" property="amount"/>
<result column="customer_id" property="customerId"/>

<!-- 订单关联客户:第一层分步查询,嵌套客户的订单分步查询 -->
<association property="customer"
javaType="com.example.mybatis.bean.Customer"
select="com.example.mybatis.mapper.OrderCustomerStepMapper.getCustomerById"
column="customer_id"
resultMap="CustomerWithOrdersRM"/> <!-- 嵌套第二层 resultMap,触发超级分步 -->
</resultMap>

<!-- 超级分步查询入口:查询订单,同时加载客户及客户的所有订单 -->
<select id="getOrderWithCustomerAndOrdersSuperStep" resultMap="OrderCustomerWithOrdersSuperStepRM">
select * from t_order where id = #{id}
</select>
</mapper>

超级分步查询执行流程

  1. 调用入口方法 getOrderWithCustomerAndOrdersSuperStep,查询目标订单基本信息
  2. 触发第一层分步查询:通过 association 调用 getCustomerById,传递 customer_id 查询客户信息
  3. 触发第二层分步查询:通过嵌套的 CustomerWithOrdersRM 中的 collection,调用 getOrdersByCustomerId,传递客户 id 查询所有订单
  4. 最终返回:订单对象 → 包含客户对象 → 客户对象包含订单集合

四、分步查询的优势

  1. 性能优化:按需加载数据,避免一次性查询大量冗余字段,支持延迟加载(仅访问关联属性时才执行分步查询)
  2. 代码解耦:复杂关联拆分为独立单表查询方法,维护成本低,可复用
  3. 灵活性高:支持多层嵌套(超级分步),满足复杂业务场景的关联查询需求

五、注意事项

  1. column 传递多参数时,格式为 column="{param1=字段1, param2=字段2}",对应目标方法的 @Param 注解参数名
  2. 嵌套分步查询时,需确保每层 select 指向的方法路径正确(命名空间 + 方法名)
  3. 延迟加载需在 MyBatis 全局配置中开启(lazyLoadingEnabled=true),否则会一次性执行所有分步查询
  4. 避免过度嵌套:多层分步查询会增加数据库连接次数,需平衡性能与业务需求