乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

resultMap详解

导学

本节学习目标:

  • 理解 resultMap 与 resultType 的本质区别,明确何时必须手写 resultMap
  • 掌握 id 与 result 子元素的分工,以及它们对缓存和嵌套映射的影响
  • 学会使用 constructor / idArg / arg 进行构造方法注入
  • 理解 autoMapping 三种行为模式(NONE / PARTIAL / FULL)的适用场景
  • 掌握 extends 继承机制,实现 resultMap 的复用与分层设计
  • 学会使用 columnPrefix 处理列名前缀冲突

定义

resultMap 是 MyBatis 中最强大的结果映射元素。当数据库列名与 Java 属性名不一致、需要映射复杂类型、或者需要开启级联关联查询时,resultType 的自动驼峰转换已无法满足需求,此时必须显式定义 resultMap。

它解决的核心痛点:

  • 列名与属性名不一致(如 user_name → userName)
  • 需要精确控制对象标识符,提升一级缓存和嵌套映射的去重性能
  • 需要将查询结果映射到不可变对象(通过构造方法注入)
  • 需要复用映射规则,避免重复 XML 配置

适用位置与核心属性

resultMap 定义在映射文件的 <mapper> 根元素下,其语法骨架如下:

<resultMap id="唯一标识" type="Java全限定类名或别名" extends="父resultMapId"
           autoMapping="PARTIAL">
    <constructor>
        <idArg column="" javaType=""/>
        <arg column="" javaType=""/>
    </constructor>
    <id property="" column=""/>
    <result property="" column=""/>
</resultMap>
属性 / 子元素必填说明
id是resultMap 在当前命名空间内的唯一标识,供 select 等语句引用
type是映射的目标 Java 类型(类全名或别名)
extends否继承另一个 resultMap 的全部映射规则,支持多层继承
autoMapping否自动映射行为:NONE(关闭)、PARTIAL(默认,仅简单属性)、FULL(包含嵌套)
<id>否标记对象标识符列,MyBatis 用它判断结果集中的两行是否代表同一个对象实例
<result>否普通属性与列的映射
<constructor>否通过构造方法创建对象,内含 <idArg> 和 <arg>
<idArg>否构造方法中的标识符参数
<arg>否构造方法中的普通参数
columnPrefix否在 <association> / <collection> 中使用,为关联列自动添加前缀

id 与 result 的本质区别

  • <id>:对应数据库主键或业务唯一键。MyBatis 在解析结果集时,利用 <id> 列的值判断两行数据是否属于同一个 Java 对象。如果两行 id 相同,MyBatis 会认为它们是同一个对象,后续只会填充其关联属性,而不会重复创建新实例。这对一级缓存、嵌套 association 和 collection 的去重至关重要。
  • <result>:对应普通属性。仅完成列值到属性值的映射,不参与对象身份判定。

autoMapping 三模式

模式行为适用场景
NONE关闭自动映射,所有属性必须显式配置需要绝对控制映射过程,避免意外字段注入
PARTIAL自动映射简单属性(非嵌套),但遇到已显式映射的属性会跳过默认推荐,平衡便利与可控性
FULL自动映射所有属性,包括嵌套对象的简单属性快速原型开发,但可能引发列名冲突

核心原理

resultMap 解析与映射流程

MyBatis 执行查询后,结果集的处理并非简单的“一行转一个对象”。当存在 resultMap 时,框架会经历以下阶段:

流程说明:

  1. 查找 resultMap:select 语句通过 resultMap 属性定位定义。
  2. 对象身份判定:若配置了 <id>,MyBatis 内部维护一个 Map<idValue, Object>,相同 id 的行会被合并到同一对象。
  3. 属性填充:<result> 和自动映射的列按类型处理器(TypeHandler)完成转换后写入属性。
  4. 构造方法分支:若配置了 <constructor>,MyBatis 直接调用匹配的构造方法,跳过无参构造 + setter 模式。

完整示例

场景说明

乐途公司员工信息表中,列名采用下划线命名(emp_name、dept_code),而 Java 实体采用驼峰命名(empName、deptCode)。此外,员工实体包含一个不可变的 id 字段,希望通过构造方法注入。我们将演示:

  • 基础 resultMap(id + result)
  • extends 复用
  • autoMapping 控制
  • columnPrefix 处理关联列前缀

操作前的数据库表结构及初始数据

-- 员工表
CREATE TABLE employee (
    emp_id      INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工编号',
    emp_name    VARCHAR(20) COMMENT '姓名',
    emp_age     INT COMMENT '年龄',
    dept_code   VARCHAR(10) COMMENT '部门编码',
    hire_date   DATE COMMENT '入职日期'
);

-- 部门表
CREATE TABLE department (
    dept_code   VARCHAR(10) PRIMARY KEY COMMENT '部门编码',
    dept_name   VARCHAR(20) COMMENT '部门名称'
);

初始数据:

emp_idemp_nameemp_agedept_codehire_date
1大翔28D012020-03-15
2白歌25D012021-06-01
3小崔30D022019-11-20
dept_codedept_name
D01研发部
D02产品部

Java 实体类

package com.flywing.entity;

import java.time.LocalDate;

public class Employee {
    private final Integer id;      // 通过构造方法注入
    private String empName;
    private Integer empAge;
    private String deptCode;
    private LocalDate hireDate;
    private Department department; // 关联对象

    public Employee(Integer id) {
        this.id = id;
    }

    // getter / setter 省略...
    public Integer getId() { return id; }
}

public class Department {
    private String deptCode;
    private String deptName;
    // getter / setter 省略...
}

完整的映射文件片段

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.flywing.mapper.EmployeeMapper">

    <!-- 基础resultMap:演示id、result、constructor -->
    <resultMap id="BaseEmployeeMap" type="com.flywing.entity.Employee"
               autoMapping="NONE">
        <!-- 通过构造方法注入不可变id -->
        <constructor>
            <idArg column="emp_id" javaType="java.lang.Integer"/>
        </constructor>
        <!-- 对象标识符:告诉MyBatis emp_id是主键,用于去重 -->
        <id property="id" column="emp_id"/>
        <result property="empName"  column="emp_name"/>
        <result property="empAge"   column="emp_age"/>
        <result property="deptCode" column="dept_code"/>
        <result property="hireDate" column="hire_date"
                javaType="java.time.LocalDate"
                typeHandler="org.apache.ibatis.type.LocalDateTypeHandler"/>
    </resultMap>

    <!-- 继承基础Map,添加部门关联,演示extends与columnPrefix -->
    <resultMap id="EmployeeWithDeptMap" type="com.flywing.entity.Employee"
               extends="BaseEmployeeMap"
               autoMapping="NONE">
        <association property="department" javaType="com.flywing.entity.Department"
                     columnPrefix="d_">
            <id property="deptCode" column="dept_code"/>
            <result property="deptName" column="dept_name"/>
        </association>
    </resultMap>

    <!-- 查询1:基础映射 -->
    <select id="selectById" resultMap="BaseEmployeeMap">
        SELECT emp_id, emp_name, emp_age, dept_code, hire_date
        FROM employee
        WHERE emp_id = #{id}
    </select>

    <!-- 查询2:带部门信息,使用JOIN,columnPrefix自动匹配d_前缀 -->
    <select id="selectWithDept" resultMap="EmployeeWithDeptMap">
        SELECT
            e.emp_id,
            e.emp_name,
            e.emp_age,
            e.dept_code,
            e.hire_date,
            d.dept_code AS d_dept_code,
            d.dept_name AS d_dept_name
        FROM employee e
        LEFT JOIN department d ON e.dept_code = d.dept_code
        WHERE e.emp_id = #{id}
    </select>

</mapper>

实际执行结果

查询1 结果(selectById 返回 BaseEmployeeMap):

emp_idemp_nameemp_agedept_codehire_date
1大翔28D012020-03-15

控制台 SQL 输出:

==>  Preparing: SELECT emp_id, emp_name, emp_age, dept_code, hire_date FROM employee WHERE emp_id = ?
==>  Parameters: 1(Integer)
<==  Columns: emp_id, emp_name, emp_age, dept_code, hire_date
<==  Row: 1, 大翔, 28, D01, 2020-03-15
<==  Total: 1

查询2 结果(selectWithDept 返回 EmployeeWithDeptMap):

emp_idemp_nameemp_agedept_codehire_datedepartment.dept_codedepartment.dept_name
1大翔28D012020-03-15D01研发部

控制台 SQL 输出:

==>  Preparing: SELECT e.emp_id, e.emp_name, e.emp_age, e.dept_code, e.hire_date, d.dept_code AS d_dept_code, d.dept_name AS d_dept_name FROM employee e LEFT JOIN department d ON e.dept_code = d.dept_code WHERE e.emp_id = ?
==>  Parameters: 1(Integer)
<==  Columns: emp_id, emp_name, emp_age, dept_code, hire_date, d_dept_code, d_dept_name
<==  Row: 1, 大翔, 28, D01, 2020-03-15, D01, 研发部
<==  Total: 1

分析

  1. <id> 的作用:在 BaseEmployeeMap 中,emp_id 被标记为 <id>。如果后续查询返回多行且 emp_id 相同(例如一对多 JOIN),MyBatis 不会重复创建 Employee 对象,而是复用已有实例,仅填充其集合属性。这直接决定了关联查询的正确性。
  2. extends 的价值:EmployeeWithDeptMap 继承了 BaseEmployeeMap 的全部映射规则,只需额外声明 association。当实体属性很多时,这种分层设计能显著减少重复 XML。
  3. columnPrefix 的便利:在 JOIN 查询中,部门表的列被别名化为 d_dept_code、d_dept_name。columnPrefix="d_" 让 association 内部只需写原始列名 dept_code,MyBatis 会自动拼接前缀查找,避免列名冲突。
  4. autoMapping="NONE":本示例关闭了自动映射,所有属性必须显式声明。这在企业级开发中更安全,能防止数据库新增敏感列被意外映射到实体。

易错场景 / 常见误区

误区正解
认为 <id> 只是语法糖,用 <result> 代替也能正常工作<id> 参与对象身份判定,缺失会导致关联查询时重复创建对象,破坏去重逻辑
extends 继承后,子 resultMap 中重复定义父级已映射的属性子 resultMap 只需定义新增属性;重复定义会覆盖父级规则,可能引发意外行为
autoMapping="FULL" 下,嵌套对象的属性也被自动映射,导致列名冲突多表 JOIN 时,列名容易重复,建议显式配置或使用 columnPrefix,慎用 FULL
构造方法注入时,<idArg> 和 <arg> 的顺序与 Java 构造参数顺序不一致MyBatis 按 XML 声明顺序匹配构造参数,顺序错误会导致类型不匹配或注入错误
以为 resultMap 的 id 属性是全局唯一的resultMap 的 id 仅在当前 Mapper 命名空间内唯一,跨命名空间引用需加前缀

面试考点

Q1:<id> 和 <result> 在 MyBatis 内部处理上有什么区别?为什么关联查询时必须配置 <id>?

A:<id> 标记的列被 MyBatis 用作对象标识符。内部有一个 Map 缓存,键为 <id> 列的值,值为已创建的 Java 对象。当结果集中出现相同 <id> 的多行(如一对多 JOIN),MyBatis 会复用该对象实例,仅向其集合属性追加元素。如果用 <result> 代替 <id>,每行都会创建新对象,导致主对象重复、集合属性丢失,查询结果条数异常膨胀。

Q2:autoMapping 的 PARTIAL 和 FULL 有什么区别?生产环境推荐哪种?

A:PARTIAL 是默认值,只对简单属性(非嵌套对象)进行自动映射,且不会覆盖已显式配置的属性;FULL 会对嵌套对象的简单属性也进行自动映射。生产环境推荐 PARTIAL 或显式关闭(NONE),因为 FULL 在多表 JOIN 场景下极易因列名重复导致数据被错误注入。

Q3:extends 继承 resultMap 时,如果父级和子级都定义了同一个 <result>,以谁为准?

A:子级定义会覆盖父级定义。MyBatis 在合并继承链时,后加载的子级属性会替换先加载的父级同名属性。因此继承时应避免重复定义同一属性,除非确实需要覆盖。

Q4:什么情况下必须使用 resultMap,而不能用 resultType?

A:以下四种场景必须手写 resultMap:(1)列名与属性名不一致且未开启全局驼峰映射;(2)需要映射复杂类型(如枚举、自定义 TypeHandler);(3)需要配置 association / collection / discriminator 等嵌套映射;(4)需要通过构造方法创建不可变对象。

小结

resultMap 是 MyBatis 结果映射体系的基石。<id> 与 <result> 的分工体现了框架对“对象身份”的精确管理;extends 提供了映射规则的分层复用能力;autoMapping 则在便利性与可控性之间提供了三档选择。掌握这些基础后,才能正确理解后续 association、collection 等高级映射的行为边界。

下一章引子

基础映射规则已就绪,但真实业务中对象很少孤立存在。下一节将深入讲解 一对一关联查询 association,对比嵌套 Select 与嵌套结果映射两种实现方式,并揭示它们对性能的潜在影响。

上一页
本章定位
下一页
association