MyBatis 中 resultType 和 resultMap 有什么区别?
简化版
resultType 指定每行直接映射成的 Java 类型,适合列名与属性名基本一致的简单结果;resultMap 显式描述列、属性、构造器、关联和集合关系,适合字段不一致或复杂对象图。同一查询通常在 resultType 与 resultMap 中选择一种,复杂映射优先使用可复用的 resultMap。
详细版
resultType 依赖自动映射,可通过 SQL 别名或 mapUnderscoreToCamelCase 处理常见命名差异。resultMap 可用 <id>、<result>、<association>、<collection>、<constructor>、<discriminator> 精确建模,还能组合继承已有映射。
JOIN 一对多结果会重复父表列,MyBatis 依靠 resultMap 中的 id 标识识别同一父对象并合并子集合。缺少正确 id 映射可能影响去重和性能;列名冲突则应使用别名和 columnPrefix 等方式消除歧义。
完整版教学
一、简单映射什么时候够用
<select id="findById" resultType="com.example.User">
SELECT id, user_name AS userName, email
FROM users WHERE id = #{id}
</select>
列别名与 Java 属性一致时,自动映射就能完成赋值。若全局开启下划线转驼峰,user_name 也可映射到 userName,但显式别名更容易看出 SQL 契约。
| 维度 | resultType | resultMap |
|---|---|---|
| 映射方式 | 自动按列名/别名匹配属性 | 显式声明列到属性 |
| 适用结果 | 单表或平面 DTO | 复杂列名、构造器、关联、集合、多态 |
| 可复用性 | 依赖每条 SQL 的列别名 | 可作为映射蓝图复用 |
| 一对多组装 | 不擅长 | 通过 <collection> 和 <id> 识别对象 |
| 维护成本 | 简单但隐式 | 啰嗦但边界清楚 |
记忆钩子:resultType 像“自动填表”,列名对上就行;resultMap 像“施工图纸”,复杂对象图必须画清楚。
二、resultMap 的基本结构
<resultMap id="userMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
</resultMap>
id 不只是普通字段声明,它帮助 MyBatis 在嵌套结果中识别对象身份,并可提升映射性能。主键或能唯一标识对象的列应准确标为 id。
三、association 与 collection
association 映射“一个”关联对象,collection 映射“多个”元素。两者既可使用嵌套 resultMap 消化 JOIN 结果,也可用 select 属性触发另一条查询。
嵌套查询写法直观,但遍历父列表时容易产生 N+1;JOIN 嵌套结果只需一次 SQL,却会产生重复行和分页困难。选择要基于数据量、查询形态和分页语义。
四、JOIN 结果如何组装
<resultMap id="orderMap" type="Order">
<id property="id" column="order_id"/>
<collection property="items" ofType="OrderItem">
<id property="id" column="item_id"/>
<result property="name" column="item_name"/>
</collection>
</resultMap>
多行拥有相同 order_id 时,MyBatis 复用父 Order 并向 items 添加不同子对象。父子两层都应提供可靠 id;一对多 JOIN 直接做数据库分页可能截断子集合,常需先分页父 ID 再查询明细。
JOIN 结果 3 行:
order_id=10, item_id=1
order_id=10, item_id=2
order_id=11, item_id=3
正确 resultMap:
生成 2 个 Order
Order(10).items = [1, 2]
Order(11).items = [3]
如果父级没有正确标 <id property="id" column="order_id"/>,MyBatis 难以判断哪些行属于同一个父对象,可能重复创建父对象或增加映射开销。这个 <id> 的意义不只是“映射主键字段”,还关系到嵌套结果的去重和合并。
五、自动映射的边界
自动映射方便,但列名冲突或 SQL 新增同名列时可能产生意外赋值。复杂 JOIN 应显式列出字段,避免 SELECT *,并按前缀区分不同表。
不可变对象可使用 constructor 映射;多态结果可使用 discriminator,但若映射过于复杂,拆成明确查询或 DTO 往往更易维护。
六、列冲突、前缀和 DTO 取舍
复杂 JOIN 最怕 SELECT *。比如 users 和 orders 都有 id、created_at,自动映射时如果没有别名,列名冲突会让属性来源变得不清楚。更稳妥的写法是给列加前缀,如 u_id、o_id,再在 resultMap 中明确 column,或者配合 columnPrefix 复用子 resultMap。
接口只需要展示字段时,不一定要还原完整领域对象图。比如订单列表只展示订单号、用户名、总金额,用一个平面 OrderListDTO 加 resultType 或简单 resultMap 反而更清楚。只有需要复用复杂关联关系、构造嵌套对象、处理一对多集合时,才值得引入较完整的 resultMap。
七、常见误区与追问
- 误区:resultType 比 resultMap 高级或更推荐。 两者没有高低,resultType 适合简单平面结果,resultMap 适合复杂显式映射。
- 误区:开启驼峰映射后所有 JOIN 都不用 resultMap。 JOIN 常有列名冲突、一对多合并和别名歧义,仍需要显式映射。
- 追问:
<id>标签为什么重要? 它帮助 MyBatis 识别对象身份,尤其在嵌套结果中用于父子对象去重和集合合并。 - 追问:一对多 JOIN 分页为什么危险? 数据库
LIMIT限制的是 JOIN 后的行数,不是父对象数,可能截断某个父对象的子集合。 - 误区:
SELECT *配 resultMap 也没问题。 复杂查询应显式列出字段并加别名,避免同名列和表结构变化破坏映射。 - 追问:什么时候用 DTO 而不是复杂 resultMap? 当接口只需要平面展示字段时,DTO 更简单稳定;完整对象图适合真正需要关联语义的业务。
八、加强记忆
resultType 依赖自动映射,适合平面简单结果;resultMap 是显式映射蓝图,能处理构造器、association、collection 和多态。JOIN 一对多时,父子 id 是对象去重组装的关键,分页还要防止子集合被截断。