前言
在使用 MyBatis 开发 Java 项目时,我们经常会遇到这样的场景:
数据库中有一张主表,例如:
text
车辆配置表
同时还有很多关联数据:
text
配置
├── 对公支付控制
├── 供应商列表
├── 适用部门列表
├── 适用职级列表
├── 适用个人列表
├── 不适用个人列表
├── 不适用角色列表
├── 时间控制列表
└── 地点控制列表
最终我们希望 MyBatis 查询数据库后,直接组装成一个完整的 Java VO。
这时候,MyBatis 中的 resultMap、association 和 collection 就发挥了重要作用。
本文结合一个实际项目中的 MyBatis XML,如下:
xml
<!-- 根据配置id查询配置详情 -->
<resultMap id="ConfigDetail" type="com.cmbchina.car.controller.bean.carconfig.QueryCarConfigDetailVo">
<!-- 对公支付控制内容 -->
<association property="corporatePayControl"
column="{configId = configId, userId = userId}"
javaType="com.cmbchina.car.controller.bean.carconfig.CorporatePayControlVo"
select="selectCorporatePayControl">
</association>
<!-- 供应商列表 -->
<collection property="supplierList"
column="supplier"
ofType="com.cmbchina.car.controller.bean.carconfig.SupplierVo"
javaType="ArrayList"
select="selectSuppliers">
</collection>
<!-- 适用部门列表 -->
<collection property="deptList"
column="configId"
ofType="com.cmbchina.car.controller.bean.carconfig.ConfigUseRangeVo"
javaType="ArrayList"
select="selectApplyDept">
</collection>
<!-- 适用职级列表 -->
<collection property="rankList"
column="configId"
ofType="com.cmbchina.car.controller.bean.carconfig.ConfigUseRangeVo"
javaType="ArrayList"
select="selectApplyRank">
</collection>
<!-- 适用个人列表 -->
<collection property="applyUsers"
column="configId"
ofType="com.cmbchina.car.controller.bean.carconfig.ConfigUseRangeVo"
javaType="ArrayList"
select="selectApplyUsers">
</collection>
<!-- 不适用个人列表 -->
<collection property="notApplyUsers"
column="configId"
ofType="com.cmbchina.car.controller.bean.carconfig.ConfigUseRangeVo"
javaType="ArrayList"
select="selectNotApplyUsers">
</collection>
<!-- 不适用角色列表 -->
<collection property="notApplyRoles"
column="configId"
ofType="com.cmbchina.car.controller.bean.carconfig.ConfigUseRangeVo"
javaType="ArrayList"
select="selectNotApplyRoles">
</collection>
<!-- 时间控制列表 -->
<collection property="timeList"
column="configId"
ofType="com.cmbchina.car.controller.bean.carconfig.TimeControlVo"
javaType="ArrayList"
select="selectTimeByConfigId">
</collection>
<!-- 地点控制列表 -->
<collection property="positionList"
column="{configId = configId ,locationType = locationType}"
ofType="com.cmbchina.car.controller.bean.carconfig.PositionControlVo"
javaType="ArrayList"
select="selectPositionByConfigId">
</collection>
</resultMap>
<select id="queryCarConfigDetail" resultMap="ConfigDetail">
select car_config_id configId,
supplier_no supplier,
#{userId} userId,
car_config_id,
car_scene_id,
config_name,
config_desc,
sub_company_no,
sub_company_name,
car_type,
scene_type,
order_limit_amount,
day_limit_amount,
day_limit_number,
if(order_limit_amount is null, '0', '1') enableOrderAmount,
if(day_limit_amount is null, '0', '1') enableDayAmount,
if(day_limit_number is null, '0', '1') enableDayNumber,
dd_regulation_id,
dd_scene_type,
enable_personal_limit,
if(state = '1', true, false) enable,
enable_corporate_pay_control,
enable_time_control,
enable_position_control,
position_type,
location_type locationType,
enable_apply_control,
enable_book,
priority
from car_bsc_car_scene_config
<if test="list != null and list.size()>0">
where car_config_id in
<foreach collection="list" item="item" separator="," open="(" close=")">
#{item}
</foreach>
</if>
</select>
<select id="selectCorporatePayControl" resultType="com.cmbchina.car.controller.bean.carconfig.CorporatePayControlVo">
with cost_center as (
select cost_center_no,
' ' as cost_center_name,
cost_center_no_upp as upp_cost_center_no,
' ' as upp_cost_center_name
from car_bsc_company_emp where user_id = #{userId}
)
select a.*,b.*
from car_bsc_car_scene_config_corporate_control a
left join cost_center b on true
where a.car_config_id = #{configId}
</select>
<select id="selectSuppliers" resultType="com.cmbchina.car.controller.bean.carconfig.SupplierVo">
select supplier_no,supplier_name from car_bsc_supplier_base where position(supplier_no in #{supplier}) != 0 and state = '1'
</select>
<select id="selectApplyDept" resultType="com.cmbchina.car.controller.bean.carconfig.ConfigUseRangeVo">
select car_scene_config_use_range_id,type,code,name from car_bsc_car_scene_config_use_range
where type in ('1','5') and apply = '1' and car_config_id = #{configId} and state = '1'
</select>
<select id="selectApplyRank" resultType="com.cmbchina.car.controller.bean.carconfig.ConfigUseRangeVo">
select car_scene_config_use_range_id,code,name from car_bsc_car_scene_config_use_range
where type = '3' and apply = '1' and car_config_id = #{configId} and state = '1'
</select>
<select id="selectApplyUsers" resultType="com.cmbchina.car.controller.bean.carconfig.ConfigUseRangeVo">
select car_scene_config_use_range_id,code,name from car_bsc_car_scene_config_use_range
where type = '2' and apply = '1' and car_config_id = #{configId} and state = '1'
</select>
<select id="selectNotApplyUsers" resultType="com.cmbchina.car.controller.bean.carconfig.ConfigUseRangeVo">
select car_scene_config_use_range_id,code,name from car_bsc_car_scene_config_use_range
where type = '2' and apply = '2' and car_config_id = #{configId} and state = '1'
</select>
<select id="selectNotApplyRoles" resultType="com.cmbchina.car.controller.bean.carconfig.ConfigUseRangeVo">
select car_scene_config_use_range_id,code,name from car_bsc_car_scene_config_use_range
where type = '4' and apply = '2' and car_config_id = #{configId} and state = '1'
</select>
详细介绍这几个标签的作用以及它们背后的执行机制。
一、先理解 resultMap 是什么
在 MyBatis 中,最简单的查询可能是:
xml
<select id="queryUser" resultType="User">
select id, name, age
from user
</select>
如果数据库字段和 Java 属性可以直接对应,那么使用 resultType 就足够了。
例如数据库:
text
id | name | age
Java:
java
public class User {
private Long id;
private String name;
private Integer age;
}
MyBatis 可以自动完成:
text
数据库字段
↓
Java 属性
但是,如果对象结构比较复杂,例如:
java
public class QueryCarConfigDetailVo {
private Long configId;
private String configName;
private CorporatePayControlVo corporatePayControl;
private List<SupplierVo> supplierList;
private List<ConfigUseRangeVo> deptList;
private List<ConfigUseRangeVo> rankList;
private List<ConfigUseRangeVo> applyUsers;
private List<ConfigUseRangeVo> notApplyUsers;
private List<ConfigUseRangeVo> notApplyRoles;
private List<TimeControlVo> timeList;
private List<PositionControlVo> positionList;
}
那么简单的 resultType 就不够用了。
这时候可以使用:
xml
<resultMap>
resultMap 可以理解为:
告诉 MyBatis:数据库查询结果应该如何组装成 Java 对象。
二、association 和 collection 分别是什么?
先记住最核心的一句话:
text
association → 一个对象
collection → 一个集合
也可以理解为:
| MyBatis 标签 | Java 类型 | 关系 |
|---|---|---|
association |
一个 Java 对象 | 一对一 |
collection |
List / Set | 一对多 |
例如:
java
private CorporatePayControlVo corporatePayControl;
它是一个对象,因此使用:
xml
<association>
而:
java
private List<ConfigUseRangeVo> deptList;
它是一个集合,因此使用:
xml
<collection>
三、结合实际代码理解 association
例如项目中的代码:
xml
<association property="corporatePayControl"
column="{configId = configId, userId = userId}"
javaType="com.cmbchina.car.controller.bean.carconfig.CorporatePayControlVo"
select="selectCorporatePayControl">
</association>
这段代码可以拆成几个部分理解。
1. property:最终放到哪个属性
xml
property="corporatePayControl"
表示查询结果最终要放到:
java
QueryCarConfigDetailVo.corporatePayControl
相当于:
java
QueryCarConfigDetailVo vo = new QueryCarConfigDetailVo();
vo.setCorporatePayControl(...);
2. javaType:这个属性是什么类型
xml
javaType="com.cmbchina.car.controller.bean.carconfig.CorporatePayControlVo"
说明:
java
corporatePayControl
对应:
java
CorporatePayControlVo
也就是说:
text
QueryCarConfigDetailVo
│
└── corporatePayControl
│
└── CorporatePayControlVo
3. select:调用哪个 SQL 查询
这里最关键:
xml
select="selectCorporatePayControl"
表示:
查询主对象的时候,如果需要组装
corporatePayControl,再调用selectCorporatePayControl这个 SQL。
项目中对应的 SQL 是:
xml
<select id="selectCorporatePayControl"
resultType="com.cmbchina.car.controller.bean.carconfig.CorporatePayControlVo">
with cost_center as (
select cost_center_no,
' ' as cost_center_name,
cost_center_no_upp as upp_cost_center_no,
' ' as upp_cost_center_name
from car_bsc_company_emp
where user_id = #{userId}
)
select a.*, b.*
from car_bsc_car_scene_config_corporate_control a
left join cost_center b on true
where a.car_config_id = #{configId}
</select>
因此整个过程可以理解为:
text
queryCarConfigDetail
↓
查询主配置
↓
QueryCarConfigDetailVo
↓
发现需要 corporatePayControl
↓
调用 selectCorporatePayControl
↓
查询 CorporatePayControlVo
↓
设置到 corporatePayControl
四、column 可以给嵌套查询传参数
这里还有一个非常重要的地方:
xml
column="{configId = configId, userId = userId}"
这表示:
将当前主查询结果中的
configId和userId作为参数,传递给selectCorporatePayControl。
例如主查询得到:
text
configId = 10001
userId = U001
那么调用子查询时,就相当于:
text
selectCorporatePayControl(
configId = 10001,
userId = U001
)
于是子 SQL 中:
sql
where a.car_config_id = #{configId}
可以拿到:
text
10001
而:
sql
where user_id = #{userId}
可以拿到:
text
U001
五、为什么这里可以传多个参数?
MyBatis 的 column 不仅可以写:
xml
column="configId"
也可以写:
xml
column="{configId = configId, userId = userId}"
前者表示传一个参数:
text
configId
后者表示传多个参数:
text
configId → configId
userId → userId
可以把它理解成一个参数 Map:
java
Map<String, Object> params = new HashMap<>();
params.put("configId", 当前行的configId);
params.put("userId", 当前行的userId);
然后传给:
text
selectCorporatePayControl
因此子 SQL 可以使用:
sql
#{configId}
#{userId}
六、主查询中的 userId 从哪里来?
项目代码中还有一个容易被忽略的地方:
sql
select car_config_id configId,
supplier_no supplier,
#{userId} userId,
car_config_id,
...
from car_bsc_car_scene_config
这里:
sql
#{userId}
并不是数据库字段。
它是 MyBatis Mapper 方法传进来的参数。
例如 Mapper 可能是:
java
List<QueryCarConfigDetailVo> queryCarConfigDetail(
@Param("list") List<Long> list,
@Param("userId") String userId
);
那么:
sql
#{userId} userId
就相当于把 Java 方法参数 userId 放进 SQL 查询结果中。
例如:
text
configId | supplier | userId
---------+----------+-------
10001 | DiDi | U001
这样后面的:
xml
column="{configId = configId, userId = userId}"
就能够同时获得:
text
configId = 10001
userId = U001
七、collection 是什么?
接下来看看:
xml
<collection property="deptList"
column="configId"
ofType="com.cmbchina.car.controller.bean.carconfig.ConfigUseRangeVo"
javaType="ArrayList"
select="selectApplyDept">
</collection>
这就是 collection。
它与 association 最大的区别是:
text
association → 一个对象
collection → 多个对象
例如:
java
private List<ConfigUseRangeVo> deptList;
一个车辆配置可能对应多个部门:
text
配置 10001
│
├── 技术部
├── 财务部
├── 人力资源部
└── 风险管理部
因此 Java 中需要:
java
List<ConfigUseRangeVo>
而不是:
java
ConfigUseRangeVo
所以使用:
xml
<collection>
八、collection 中的 property
例如:
xml
property="deptList"
表示:
查询出来的集合最终放到
QueryCarConfigDetailVo.deptList。
相当于:
java
vo.setDeptList(list);
九、collection 中的 ofType
代码:
xml
ofType="com.cmbchina.car.controller.bean.carconfig.ConfigUseRangeVo"
表示:
这个集合里面的元素是什么类型。
也就是说:
java
List<ConfigUseRangeVo>
其中:
text
List
↓
ConfigUseRangeVo
因此:
text
javaType
和:
text
ofType
可以简单理解为:
text
javaType → 整个属性是什么类型
ofType → 集合中的元素是什么类型
例如:
xml
javaType="ArrayList"
ofType="ConfigUseRangeVo"
表示:
java
ArrayList<ConfigUseRangeVo>
十、collection 中的 column
例如:
xml
column="configId"
表示:
使用当前主查询结果中的
configId,作为参数传给子查询。
对应的子 SQL:
xml
<select id="selectApplyDept"
resultType="com.cmbchina.car.controller.bean.carconfig.ConfigUseRangeVo">
select car_scene_config_use_range_id,
type,
code,
name
from car_bsc_car_scene_config_use_range
where type in ('1','5')
and apply = '1'
and car_config_id = #{configId}
and state = '1'
</select>
因此执行过程就是:
text
主查询
configId = 10001
↓
selectApplyDept(10001)
↓
where car_config_id = 10001
↓
查询多个部门
↓
List<ConfigUseRangeVo>
↓
deptList
十一、项目中的多个 collection
项目中实际上使用了多个 collection:
1. 适用部门
xml
<collection property="deptList"
column="configId"
select="selectApplyDept">
</collection>
2. 适用职级
xml
<collection property="rankList"
column="configId"
select="selectApplyRank">
</collection>
3. 适用个人
xml
<collection property="applyUsers"
column="configId"
select="selectApplyUsers">
</collection>
4. 不适用个人
xml
<collection property="notApplyUsers"
column="configId"
select="selectNotApplyUsers">
</collection>
5. 不适用角色
xml
<collection property="notApplyRoles"
column="configId"
select="selectNotApplyRoles">
</collection>
6. 时间控制
xml
<collection property="timeList"
column="configId"
select="selectTimeByConfigId">
</collection>
7. 地点控制
地点控制稍微特殊一些:
xml
<collection property="positionList"
column="{configId = configId, locationType = locationType}"
select="selectPositionByConfigId">
</collection>
这里传递了两个参数:
text
configId
locationType
所以子 SQL 可以同时使用:
sql
#{configId}
#{locationType}
十二、整个 resultMap 到底做了什么?
现在把整个 resultMap 放在一起看:
xml
<resultMap id="ConfigDetail"
type="com.cmbchina.car.controller.bean.carconfig.QueryCarConfigDetailVo">
<association property="corporatePayControl"
column="{configId = configId, userId = userId}"
javaType="CorporatePayControlVo"
select="selectCorporatePayControl">
</association>
<collection property="supplierList"
column="supplier"
ofType="SupplierVo"
javaType="ArrayList"
select="selectSuppliers">
</collection>
<collection property="deptList"
column="configId"
ofType="ConfigUseRangeVo"
javaType="ArrayList"
select="selectApplyDept">
</collection>
<collection property="rankList"
column="configId"
ofType="ConfigUseRangeVo"
javaType="ArrayList"
select="selectApplyRank">
</collection>
<collection property="applyUsers"
column="configId"
ofType="ConfigUseRangeVo"
javaType="ArrayList"
select="selectApplyUsers">
</collection>
<collection property="notApplyUsers"
column="configId"
ofType="ConfigUseRangeVo"
javaType="ArrayList"
select="selectNotApplyUsers">
</collection>
<collection property="notApplyRoles"
column="configId"
ofType="ConfigUseRangeVo"
javaType="ArrayList"
select="selectNotApplyRoles">
</collection>
<collection property="timeList"
column="configId"
ofType="TimeControlVo"
javaType="ArrayList"
select="selectTimeByConfigId">
</collection>
<collection property="positionList"
column="{configId = configId, locationType = locationType}"
ofType="PositionControlVo"
javaType="ArrayList"
select="selectPositionByConfigId">
</collection>
</resultMap>
它实际上是在描述这样一个对象:
text
QueryCarConfigDetailVo
│
├── 基础配置字段
│
├── corporatePayControl
│ └── CorporatePayControlVo
│
├── supplierList
│ └── List<SupplierVo>
│
├── deptList
│ └── List<ConfigUseRangeVo>
│
├── rankList
│ └── List<ConfigUseRangeVo>
│
├── applyUsers
│ └── List<ConfigUseRangeVo>
│
├── notApplyUsers
│ └── List<ConfigUseRangeVo>
│
├── notApplyRoles
│ └── List<ConfigUseRangeVo>
│
├── timeList
│ └── List<TimeControlVo>
│
└── positionList
└── List<PositionControlVo>
所以 resultMap 本质上就是:
Java 对象的组装说明书。
十三、完整的执行流程
假设主查询:
sql
select car_config_id configId,
supplier_no supplier,
#{userId} userId,
location_type locationType,
...
from car_bsc_car_scene_config
查询出来:
text
configId = 10001
supplier = DiDi
userId = U001
locationType = 1
MyBatis 接下来根据 resultMap 组装对象。
首先创建:
java
QueryCarConfigDetailVo vo =
new QueryCarConfigDetailVo();
然后处理基础字段:
java
vo.setConfigId(10001);
接下来处理:
xml
<association>
调用:
text
selectCorporatePayControl
参数:
text
configId = 10001
userId = U001
得到:
java
CorporatePayControlVo
然后:
java
vo.setCorporatePayControl(...);
接下来处理:
xml
<collection property="deptList">
调用:
text
selectApplyDept
参数:
text
configId = 10001
得到:
java
List<ConfigUseRangeVo>
然后:
java
vo.setDeptList(...);
后面继续:
text
selectApplyRank
selectApplyUsers
selectNotApplyUsers
selectNotApplyRoles
selectTimeByConfigId
selectPositionByConfigId
最终形成一个完整的:
java
QueryCarConfigDetailVo
十四、为什么不直接使用 JOIN?
看到这里可能会产生一个问题:
这些数据本来都是关联表,为什么不使用
LEFT JOIN一次性查出来?
例如:
sql
select ...
from car_bsc_car_scene_config a
left join car_bsc_car_scene_config_use_range b
on a.car_config_id = b.car_config_id
当然可以。
但是问题在于:
一个主表记录可能同时存在多个一对多关系。
例如一个配置:
text
3 个部门
5 个职级
10 个适用人员
2 个不适用人员
4 个角色
如果把这些表全部 JOIN:
text
3 × 5 × 10 × 2 × 4
最终可能产生:
text
1200
条组合记录。
但实际上我们只需要:
text
1 个配置
3 个部门
5 个职级
10 个用户
2 个排除用户
4 个角色
也就是说:
text
真正的数据量
≠
多表 JOIN 后产生的数据行数
这就是多表 JOIN 在复杂一对多场景中容易出现的数据膨胀问题。
十五、这种写法实际上是一种"分开查询、对象组装"的方案
现在这段 MyBatis 的结构就很好理解了:
text
主配置
│
↓
car_bsc_car_scene_config
│
↓
QueryCarConfigDetailVo
│
┌──────────────┼──────────────┐
↓ ↓ ↓
对象查询 集合查询 集合查询
│ │ │
↓ ↓ ↓
对公支付控制 部门列表 职级列表
优点是:
1. SQL 比较清晰
每个 SQL 只负责自己的业务。
2. Java 对象结构清晰
一个字段对应一个对象或者一个集合。
3. 避免复杂 JOIN
特别是多个一对多关系同时存在的时候。
4. 业务维护方便
例如以后增加:
text
黑名单
只需要增加:
xml
<collection property="blackList"
column="configId"
select="selectBlackList">
</collection>
而不需要修改一个非常复杂的多表 JOIN。
十六、但是这种方案存在一个非常重要的问题:N+1 查询
虽然 association + collection + select 非常方便,但是它也存在一个经典问题:
N+1 Query Problem(N+1 查询问题)
例如:
text
主查询查询出 100 个配置
每个配置又需要查询:
text
corporatePayControl
supplierList
deptList
rankList
applyUsers
notApplyUsers
notApplyRoles
timeList
positionList
假设有 9 个子查询。
那么可能出现:
text
1 次主查询
+
100 × 9 次子查询
=
901 次 SQL
也就是:
text
N + 1
这在数据量比较大的情况下可能造成严重的数据库压力。
十七、为什么 N+1 查询性能比较差?
假设一次 SQL 平均耗时:
text
10ms
理论上:
text
1 次查询 ≈ 10ms
但是如果执行:
text
901 次查询
即使每次只有 10ms:
text
901 × 10ms
≈ 9010ms
≈ 9 秒
实际项目中还要考虑:
text
数据库连接池
网络延迟
数据库负载
锁等待
SQL 执行计划
并发请求
所以实际耗时可能更高。
因此:
association/collection + select不是性能差,而是需要根据数据量和查询场景谨慎使用。
十八、什么时候适合使用 association + collection?
比较适合:
text
查询单个详情
例如:
text
根据 configId 查询配置详情
因为:
text
一个 configId
→ 一个主对象
→ 若干关联数据
这种情况下即使执行多个子查询,数量也是有限的。
例如:
text
1 次主查询
+
9 次子查询
=
10 次 SQL
通常是可以接受的。
十九、什么时候应该谨慎使用?
如果是:
text
查询列表
例如:
text
查询 1000 个配置
同时每个配置又有多个:
text
collection
那么就要特别小心。
可能变成:
text
1
+
1000 × 9
=
9001 次 SQL
这种情况下就应该考虑其他方案,例如:
text
批量查询
JOIN
IN 查询
一次查询后 Java 分组
二十、一个更推荐的批量查询思路
例如主查询:
text
查询 100 个 configId
不要:
text
每个 configId 查询一次部门
而可以:
sql
select *
from car_bsc_car_scene_config_use_range
where car_config_id in (...)
一次把所有部门查出来。
然后 Java 根据:
text
car_config_id
进行分组:
java
Map<Long, List<ConfigUseRangeVo>> deptMap;
最终:
text
configId = 10001
↓
deptMap.get(10001)
configId = 10002
↓
deptMap.get(10002)
这样可以把:
text
100 次 SQL
降低为:
text
1 次 SQL
这也是实际项目中优化 N+1 查询非常常见的方法。
二十一、association 和 collection 的核心区别总结
可以通过下面这个表快速记忆:
| 特性 | association | collection |
|---|---|---|
| 用途 | 关联一个对象 | 关联多个对象 |
| Java 类型 | Object | List / Set |
| 典型关系 | 一对一 | 一对多 |
| 示例 | CorporatePayControlVo |
List<ConfigUseRangeVo> |
| 常用属性 | property、column、javaType、select | property、column、ofType、javaType、select |
| 是否可以嵌套查询 | 可以 | 可以 |
| 是否可能产生 N+1 | 是 | 是 |
二十二、最重要的几个属性
实际开发中,可以重点记住:
xml
<association
property="..."
column="..."
javaType="..."
select="..."
/>
和:
xml
<collection
property="..."
column="..."
javaType="..."
ofType="..."
select="..."
/>
可以记成:
text
property
↓
结果放到 Java 对象的哪个属性
column
↓
给嵌套 SQL 传什么参数
javaType
↓
这个属性本身是什么类型
ofType
↓
collection 中每个元素是什么类型
select
↓
调用哪个 SQL
二十三、用一句话理解整个机制
如果让我把这段 MyBatis 代码压缩成一句话:
主 SQL 负责查询主对象,
resultMap负责对象组装,association负责组装一个关联对象,collection负责组装一个关联集合,而select则负责告诉 MyBatis 到哪个 SQL 中查询这些关联数据。
整个过程可以抽象成:
text
数据库
│
↓
主查询
│
↓
QueryCarConfigDetailVo
│
├── association
│ ↓
│ 一个关联对象
│
├── collection
│ ↓
│ 一个关联集合
│
├── collection
│ ↓
│ 一个关联集合
│
└── collection
↓
一个关联集合
因此,看到 MyBatis 中:
xml
<association>
你可以第一时间想到:
"这是给 Java 对象里的某个对象属性赋值。"
看到:
xml
<collection>
就想到:
"这是给 Java 对象里的某个 List/Set 属性赋值。"
看到:
xml
select="xxx"
就想到:
"这个属性的数据需要再调用另一个 SQL 查询。"
看到:
xml
column="{a = a, b = b}"
就想到:
"这里是在给嵌套 SQL 传多个参数。"
掌握这几个核心概念之后,再去阅读复杂的 MyBatis resultMap,基本就不会觉得这些 XML 很难理解了。
二十四、最后总结
MyBatis 的 association 和 collection 本质上都是为了解决一个问题:
如何把数据库中的关联数据,组装成结构复杂的 Java 对象。
最核心的关系是:
text
association
↓
一个对象
collection
↓
多个对象
而:
text
resultMap
则负责描述整个对象的组装规则。
例如:
java
QueryCarConfigDetailVo
可以最终被组装成:
text
QueryCarConfigDetailVo
│
├── corporatePayControl : CorporatePayControlVo
│
├── supplierList : List<SupplierVo>
│
├── deptList : List<ConfigUseRangeVo>
│
├── rankList : List<ConfigUseRangeVo>
│
├── applyUsers : List<ConfigUseRangeVo>
│
├── notApplyUsers : List<ConfigUseRangeVo>
│
├── notApplyRoles : List<ConfigUseRangeVo>
│
├── timeList : List<TimeControlVo>
│
└── positionList : List<PositionControlVo>
这就是 MyBatis resultMap + association + collection 最典型的使用场景。
不过在实际开发中,还需要特别注意:
text
association/collection + select
可能带来的:
text
N+1 查询问题
因此:
详情查询可以考虑这种写法;大批量列表查询则应该重点关注 SQL 次数和数据库压力,必要时采用批量查询、JOIN 或 Java 分组的方式进行优化。