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

    • 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

导学

本节将深入掌握MyBatis中最强大的结果映射机制。你将学会resultMap的完整结构,理解id与result子元素的区别,掌握autoMapping的行为控制,了解extends继承机制,并能够用resultMap解决列名与属性名不一致的映射难题。association、collection、discriminator等高级特性将放到第05章讲解。

定义

resultMap是SQL映射文件中用于显式定义结果集与Java对象映射关系的元素。在JDBC原始写法中,当数据库列名与Java属性名不一致时,开发者需要在ResultSet遍历代码中硬编码列名与属性的对应关系,代码冗长且难以维护。resultMap将这种对应关系声明化:在XML中一次性定义好"哪一列映射到哪个属性",所有引用该resultMap的查询都会遵循这一规则,实现映射逻辑的集中管理与复用。

适用位置与核心属性

resultMap元素书写在映射文件的<mapper>根标签内部,可被多个select元素通过resultMap属性引用。

属性是否必填说明
id是resultMap的唯一标识,供select元素的resultMap属性引用
type是映射目标Java类的全限定名或别名
autoMapping否是否开启自动映射,默认根据全局配置(通常为true)
extends否继承另一个resultMap的映射规则,实现复用

resultMap内部子元素(本章重点讲解前两个):

子元素说明
<id>标识主键列与属性的映射,用于缓存和结果集唯一性判断
<result>标识普通列与属性的映射
<constructor>通过构造函数参数映射(第05章讲解)
<association>一对一关联映射(第05章讲解)
<collection>一对多关联映射(第05章讲解)
<discriminator>鉴别器映射(第05章讲解)

核心原理

MyBatis解析resultMap时,会构建一棵映射规则树,在结果集处理阶段按规则逐行、逐列地将JDBC数据转换为Java对象。

  1. 解析阶段:MyBatis启动时解析XML,将每个<id>和<result>转换为ResultMapping对象,注册到Configuration中。
  2. 引用阶段:select元素通过resultMap属性找到对应的映射规则。
  3. 结果集处理:ResultSetHandler遍历ResultSet的每一行。
  4. 对象创建或复用:根据<id>元素指定的主键列判断当前行是否属于已创建的对象(对普通查询而言,每行都是新对象)。
  5. 显式映射:先处理<id>,再处理<result>,通过反射调用setter方法赋值。
  6. 自动映射补充:若开启autoMapping,未在resultMap中显式声明但列名与属性名匹配的列会被自动映射。
  7. 返回:所有行处理完毕后,返回对象列表。

完整示例

场景说明

乐途公司技术部的数据库表采用下划线命名(stu_id、stu_name、stu_age),但Java实体类采用驼峰命名(stuId、stuName、stuAge)。resultType无法处理这种差异,必须通过resultMap显式建立映射关系。此外,系统还需要复用基础映射规则扩展出包含成绩信息的完整映射。

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

CREATE TABLE student (
    stu_id INT PRIMARY KEY AUTO_INCREMENT,
    stu_name VARCHAR(20),
    stu_age INT,
    stu_major VARCHAR(20),
    stu_score DECIMAL(5,2)
);

INSERT INTO student (stu_name, stu_age, stu_major, stu_score) VALUES
('大翔', 22, '计算机科学', 95.5),
('白歌', 21, '软件工程', 88.0),
('小崔', 20, '计算机科学', 92.0),
('黄俪', 21, '信息安全', 90.5),
('李眉', 22, '软件工程', 87.0);

当前数据状态:

stu_idstu_namestu_agestu_majorstu_score
1大翔22计算机科学95.50
2白歌21软件工程88.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50
5李眉22软件工程87.00

完整的映射文件片段与Java代码

POJO类

package com.flywing.entity;

public class Student {
    private Integer stuId;
    private String stuName;
    private Integer stuAge;
    private String stuMajor;
    private Double stuScore;

    // Getter与Setter
    public Integer getStuId() { return stuId; }
    public void setStuId(Integer stuId) { this.stuId = stuId; }
    public String getStuName() { return stuName; }
    public void setStuName(String stuName) { this.stuName = stuName; }
    public Integer getStuAge() { return stuAge; }
    public void setStuAge(Integer stuAge) { this.stuAge = stuAge; }
    public String getStuMajor() { return stuMajor; }
    public void setStuMajor(String stuMajor) { this.stuMajor = stuMajor; }
    public Double getStuScore() { return stuScore; }
    public void setStuScore(Double stuScore) { this.stuScore = stuScore; }
}

Mapper接口

package com.flywing.mapper;

import com.flywing.entity.Student;
import java.util.List;

public interface StudentMapper {
    // 使用基础resultMap查询
    Student findById(Integer id);

    // 使用继承的resultMap查询
    Student findFullById(Integer id);

    // 查询列表
    List<Student> findAll();
}

映射文件 StudentMapper.xml

<?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.StudentMapper">

    <!--
        基础resultMap:映射主键和基本信息
        id子元素标识主键,result子元素标识普通列
    -->
    <resultMap id="BaseResultMap" type="com.flywing.entity.Student">
        <id     column="stu_id"    property="stuId" />
        <result column="stu_name"  property="stuName" />
        <result column="stu_age"   property="stuAge" />
    </resultMap>

    <!--
        扩展resultMap:继承BaseResultMap,增加专业和成绩映射
        extends属性实现规则复用,避免重复书写id和name的映射
    -->
    <resultMap id="FullResultMap" type="com.flywing.entity.Student" extends="BaseResultMap">
        <result column="stu_major" property="stuMajor" />
        <result column="stu_score" property="stuScore" />
    </resultMap>

    <!-- 使用基础resultMap -->
    <select id="findById" resultMap="BaseResultMap">
        SELECT stu_id, stu_name, stu_age
        FROM student
        WHERE stu_id = #{id}
    </select>

    <!-- 使用扩展resultMap -->
    <select id="findFullById" resultMap="FullResultMap">
        SELECT stu_id, stu_name, stu_age, stu_major, stu_score
        FROM student
        WHERE stu_id = #{id}
    </select>

    <!-- 使用扩展resultMap查询列表 -->
    <select id="findAll" resultMap="FullResultMap">
        SELECT stu_id, stu_name, stu_age, stu_major, stu_score
        FROM student
    </select>

</mapper>

测试代码

package com.flywing.test;

import com.flywing.entity.Student;
import com.flywing.mapper.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

public class ResultMapTest {
    public static void main(String[] args) throws Exception {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession session = factory.openSession();
        StudentMapper mapper = session.getMapper(StudentMapper.class);

        // 场景一:基础resultMap
        Student s1 = mapper.findById(1);
        System.out.println("基础resultMap查询:");
        System.out.println("  ID=" + s1.getStuId() + ",姓名=" + s1.getStuName() + ",年龄=" + s1.getStuAge());

        // 场景二:扩展resultMap
        Student s2 = mapper.findFullById(2);
        System.out.println("扩展resultMap查询:");
        System.out.println("  ID=" + s2.getStuId() + ",姓名=" + s2.getStuName()
                + ",专业=" + s2.getStuMajor() + ",分数=" + s2.getStuScore());

        // 场景三:列表查询
        List<Student> list = mapper.findAll();
        System.out.println("列表查询结果:");
        for (Student s : list) {
            System.out.println("  " + s.getStuId() + " | " + s.getStuName()
                    + " | " + s.getStuMajor() + " | " + s.getStuScore());
        }

        session.close();
    }
}

实际执行结果

控制台SQL输出

[DEBUG] com.flywing.mapper.StudentMapper.findById - ==>  Preparing: SELECT stu_id, stu_name, stu_age FROM student WHERE stu_id = ?
[DEBUG] com.flywing.mapper.StudentMapper.findById - ==> Parameters: 1(Integer)
[DEBUG] com.flywing.mapper.StudentMapper.findById - <==      Total: 1
基础resultMap查询:
  ID=1,姓名=大翔,年龄=22

[DEBUG] com.flywing.mapper.StudentMapper.findFullById - ==>  Preparing: SELECT stu_id, stu_name, stu_age, stu_major, stu_score FROM student WHERE stu_id = ?
[DEBUG] com.flywing.mapper.StudentMapper.findFullById - ==> Parameters: 2(Integer)
[DEBUG] com.flywing.mapper.StudentMapper.findFullById - <==      Total: 1
扩展resultMap查询:
  ID=2,姓名=白歌,专业=软件工程,分数=88.0

[DEBUG] com.flywing.mapper.StudentMapper.findAll - ==>  Preparing: SELECT stu_id, stu_name, stu_age, stu_major, stu_score FROM student
[DEBUG] com.flywing.mapper.StudentMapper.findAll - <==      Total: 5
列表查询结果:
  1 | 大翔 | 计算机科学 | 95.5
  2 | 白歌 | 软件工程 | 88.0
  3 | 小崔 | 计算机科学 | 92.0
  4 | 黄俪 | 信息安全 | 90.5
  5 | 李眉 | 软件工程 | 87.0

查询结果集表格(以findAll为例)

stu_idstu_namestu_agestu_majorstu_score
1大翔22计算机科学95.50
2白歌21软件工程88.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50
5李眉22软件工程87.00

分析

  1. <id>与<result>的区别:<id>用于标识主键列,MyBatis会利用它进行结果集去重和缓存键生成;<result>用于普通列。在简单查询中二者功能差异不明显,但在关联查询和缓存场景下,<id>的正确配置至关重要。
  2. extends继承:FullResultMap通过extends="BaseResultMap"复用了基础映射规则,只需补充新增字段。这在大型项目中极为实用:基础字段统一维护,扩展映射按需叠加。
  3. autoMapping的隐性作用:即使FullResultMap没有显式声明stuId、stuName、stuAge的映射(因为它们在BaseResultMap中),继承后这些规则依然生效。若关闭autoMapping且未显式声明某列,该列将被忽略。

易错场景/常见误区

误区正解
resultMap的id属性与<id>子元素混淆resultMap的id是整个映射规则的标识;<id>子元素是主键列的映射声明
认为extends会覆盖父resultMap的同名映射extends是追加而非覆盖;子resultMap中重新定义同名result会报错或产生歧义
在resultMap中遗漏<id>,全部用<result>主键应使用<id>声明,这有助于MyBatis优化缓存和结果集处理
column写Java属性名,property写数据库列名column对应数据库列名,property对应Java属性名,二者不可写反
认为resultMap比resultType性能差二者性能差异微乎其微;resultMap的显式映射在复杂场景下反而更可控
关闭autoMapping后期望未声明列自动映射autoMapping="false"时,只有resultMap中显式声明的列才会被映射,其余列被忽略

面试考点

Q1:resultMap中的<id>和<result>有什么区别?

<id>用于标识主键列与属性的映射,MyBatis用它进行结果集唯一性判断、缓存键生成和对象复用。<result>用于普通列的映射。在简单查询中二者表现相似,但在嵌套结果映射和二级缓存场景下,<id>的正确配置直接影响结果正确性。

Q2:resultMap的extends有什么作用?

extends允许一个resultMap继承另一个resultMap的所有映射规则,在此基础上追加或覆盖(不建议覆盖)新的映射。这实现了映射规则的复用,避免在多个resultMap中重复书写相同的<id>和<result>配置,提升维护性。

Q3:autoMapping在resultMap中如何工作?

当autoMapping开启(默认或显式设置true)时,MyBatis会先处理resultMap中显式声明的<id>和<result>,然后对剩余未显式声明但列名与属性名匹配的列进行自动映射。若设置为false,则只映射显式声明的列,其余忽略。

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

以下情况必须使用resultMap:数据库列名与Java属性名不一致且无法通过驼峰转换匹配;需要进行复杂的类型转换(如将VARCHAR状态码映射为枚举);需要嵌套关联映射(一对一、一对多);需要控制自动映射行为。简单的一致命名场景优先使用resultType以减少配置。

小结

resultMap是MyBatis结果映射的终极解决方案。通过<id>标识主键、<result>声明普通列映射,它彻底解决了列名与属性名不一致的难题。extends继承机制让映射规则可以分层复用,避免重复配置。autoMapping则提供了显式映射与自动映射的混合策略。掌握resultMap后,任何复杂的结果集形态都能被精确映射到Java对象中。

下一章引子

SQL语句写好了,结果映射也配置完毕,但参数是如何从Java对象传递到SQL中的?#{}和${}两种占位符看起来相似,实则天壤之别:一个安全预编译,一个危险字符串替换。下一节将深入讲解参数传递机制,通过流程图对比二者的底层差异,并现场演示SQL注入攻击,帮助你建立牢固的安全意识。

上一页
resultType
下一页
自动映射详解