自动映射详解
概述
自动映射(Auto-mapping)是 MyBatis 查询结果自动填充到 Java 对象字段的机制。你不需要为每个字段手动写 <result> 标签,MyBatis 会根据列名与属性名的匹配规则自动完成赋值。这个特性让简单的 CRUD 操作极其简洁,但也可能在不经意间引入隐蔽的 Bug。
飞翔科技的白歌在 Code Review 时就抓到过一起自动映射导致的线上数据泄露事故——那是后话。
三种自动映射模式
MyBatis 的 autoMappingBehavior 有三种模式,在全局 settings 中配置:
<settings>
<setting name="autoMappingBehavior" value="PARTIAL"/>
</settings>
模式对比表
| 模式 | 值 | 行为 | 典型场景 |
|---|---|---|---|
| NONE | 禁用 | 完全关闭自动映射,所有字段必须显式用 <result> 声明 | 对安全性要求极高的金融系统、严格 DTO 映射 |
| PARTIAL | 默认 | 只对没有定义嵌套结果映射(association/collection)的 resultMap 进行自动映射 | 绝大多数生产项目(MyBatis 推荐) |
| FULL | 全开 | 对所有字段自动映射,包括嵌套结果映射内部 | 快速原型、内部工具(需承担风险) |
自动映射决策流程
飞翔科技实战场景
场景一:白歌的正常开发(PARTIAL 模式 — 推荐)
白歌查询员工信息,resultMap 只定义了主键映射,其余字段依赖自动映射:
<!-- EmployeeMapper.xml -->
<resultMap id="baseMap" type="com.feixiang.model.Employee">
<id property="id" column="emp_id"/>
<!-- name, email, dept_name 自动映射 -->
</resultMap>
<select id="findById" resultMap="baseMap">
SELECT emp_id, name, email, dept_name FROM t_employee WHERE emp_id = #{id}
</select>
自动映射结果:
| 数据库列名 | Java 属性名 | 匹配方式 | 是否映射 |
|---|---|---|---|
emp_id | id | 显式 <id> | ✅ |
name | name | 自动(大小写不敏感) | ✅ |
email | email | 自动 | ✅ |
dept_name | deptName | 自动(下划线转驼峰,需开启 mapUnderscoreToCamelCase) | ✅(开启后) |
场景二:孔蓝踩坑 — PARTIAL 模式下的嵌套陷阱
孔蓝查询员工及其所属部门,定义了嵌套结果映射:
<resultMap id="employeeWithDept" type="com.feixiang.model.Employee">
<id property="id" column="emp_id"/>
<result property="name" column="emp_name"/>
<!-- 嵌套映射:每个员工关联一个部门 -->
<association property="department" javaType="com.feixiang.model.Department">
<id property="id" column="dept_id"/>
<result property="name" column="dept_name"/>
</association>
</resultMap>
<select id="findAllWithDept" resultMap="employeeWithDept">
SELECT e.emp_id, e.emp_name, e.email,
d.dept_id, d.dept_name
FROM t_employee e
LEFT JOIN t_department d ON e.dept_id = d.dept_id
</select>
问题:PARTIAL 模式下,外层 employeeWithDept 因为包含了 <association>,自动映射被禁用。孔蓝忘记声明 email 字段映射,导致查询结果中 email 始终为 null。
// 查询结果
Employee emp = mapper.findAllWithDept().get(0);
System.out.println(emp.getName()); // ✅ "孔蓝"
System.out.println(emp.getEmail()); // ❌ null(忘了声明映射!)
System.out.println(emp.getDepartment().getName()); // ✅ "研发部"
修复:显式声明被遗漏的字段。
<resultMap id="employeeWithDept" type="com.feixiang.model.Employee">
<id property="id" column="emp_id"/>
<result property="name" column="emp_name"/>
<result property="email" column="email"/> <!-- ← 补上! -->
<association property="department" javaType="com.feixiang.model.Department">
<id property="id" column="dept_id"/>
<result property="name" column="dept_name"/>
</association>
</resultMap>
场景三:大翔的安全审查 — 故意关闭自动映射
飞翔科技有一个薪资模块,涉及敏感数据。大翔要求必须显式声明所有字段映射,防止数据库新增敏感列被自动暴露:
<!-- 全局设置:不关,保持 PARTIAL -->
<!-- 但薪资相关的 resultMap 单独关闭自动映射 -->
<resultMap id="salaryMap" type="com.feixiang.model.Salary" autoMapping="false">
<id property="id" column="salary_id"/>
<result property="employeeId" column="emp_id"/>
<result property="baseAmount" column="base_amount"/>
<!-- 故意不声明 bonus_amount、stock_options 等敏感列 -->
<!-- 即使 SQL 查了这些列也不会自动映射到对象上 -->
</resultMap>
// 即使 SQL 中写了 SELECT * FROM t_salary(不推荐!),
// 自动映射关闭后,未声明的字段不会被赋值,杜绝数据泄露。
场景四:FULL 模式的风险演示
假设项目全局设置了 FULL:
<settings>
<setting name="autoMappingBehavior" value="FULL"/>
</settings>
黄俪在测试环境发现一个诡异现象:
<resultMap id="empMap" type="com.feixiang.model.Employee">
<id property="id" column="emp_id"/>
<association property="department" javaType="com.feixiang.model.Department">
<!-- 内部没有显式映射,但 FULL 模式会强制自动映射 -->
</association>
</resultMap>
FULL 模式下,嵌套的 <association> 内部也会自动映射。如果 SQL 返回了 dept_id 列,它会尝试映射到 Department.id。这看起来方便,但问题是:你可能根本不知道哪些列被映射了,数据库加一个列就可能意外改变对象状态。
autoMapping 属性覆盖全局设置
每个 <resultMap> 元素上可以用 autoMapping 属性覆盖全局设置:
<!-- 覆盖为开启 -->
<resultMap id="forceOnMap" type="User" autoMapping="true">
<!-- 即使全局是 NONE,这里也启用 -->
</resultMap>
<!-- 覆盖为关闭 -->
<resultMap id="forceOffMap" type="User" autoMapping="false">
<!-- 即使全局是 FULL,这里也关闭 -->
</resultMap>
自动映射与 resultType 的关系
resultType 是自动映射的最佳搭档:
<!-- resultType 不定义 resultMap,完全依赖自动映射 -->
<select id="findAll" resultType="com.feixiang.model.Employee">
SELECT emp_id AS id, name, email FROM t_employee
</select>
| 特性 | resultType | resultMap |
|---|---|---|
| 自动映射 | 始终启用(除非全局 NONE) | 受 autoMapping 属性控制 |
| 列名匹配 | 依赖别名或下划线转驼峰 | 可显式指定列-属性对应 |
| 适用场景 | 简单查询、列名与属性名一致 | 复杂映射、嵌套、构造器注入 |
| 灵活性 | 低(无法精细控制) | 高(可逐个字段控制) |
关键认知:
resultType本质上是 MyBatis 内部自动生成的一个匿名 resultMap,其 autoMapping 行为等同于该匿名 resultMap 未设置autoMapping属性时的行为——即继承全局设置。全局为 NONE 时,resultType也不会自动映射。
易错场景提醒
1. 嵌套映射中漏声明字段
最隐蔽的坑:PARTIAL 模式 + association/collection → 自动映射静默关闭。
排查技巧:如果发现某字段始终为 null,检查 resultMap 是否包含嵌套映射。
2. 大小写和下划线转换
MyBatis 默认不开启下划线转驼峰,需手动设置:
<setting name="mapUnderscoreToCamelCase" value="true"/>
否则 dept_name 不会映射到 deptName,字段值直接丢失。
3. FULL 模式下数据库加列导致数据泄露
DBA 在 t_employee 表加了 salary 列,应用层没更新 resultMap,
但 FULL 模式自动把 salary 映射到了 Employee 对象上,
前端序列化 JSON 时把薪资数据发给所有用户——严重事故!
4. columnPrefix 与自动映射的微妙互动
<resultMap id="empMap" type="Employee">
<association property="dept" columnPrefix="d_"
resultMap="deptMap"/>
</resultMap>
<!-- 如果 deptMap 开着自动映射且 dept 表有 30 个列,
你实际上映射了 30 个你不知道的字段 -->
面试考点
MyBatis 自动映射有哪三种模式?默认是哪种?
- NONE(禁用)、PARTIAL(默认,嵌套映射中禁用)、FULL(全开包括嵌套)。
PARTIAL 模式下什么情况会触发自动映射关闭?
- resultMap 中包含
<association>或<collection>嵌套映射时,该 resultMap 的自动映射会被禁用。
- resultMap 中包含
如何在单个 resultMap 上覆盖全局自动映射设置?
- 在
<resultMap>元素上使用autoMapping="true|false"属性。
- 在
resultType和自动映射是什么关系?resultType依赖自动映射完成列-属性匹配;它等价于一个没有显式autoMapping属性的匿名 resultMap,继承全局设置。
什么场景下应该关闭自动映射?
- 处理敏感数据(薪资、密码)、需要严格 DTO 映射、DTO 字段远少于数据库列时(防止意外映射)。