排查过最窝火的一类问题:SQL 在客户端里跑得好好的,字段全在;一到代码里查出来就缺几个字段,debug 半天发现 resultMap 里压根没配那几列。MyBatis 不会报错,也不会提示「我帮你自动映射了」,就是静默地把没配的列扔掉。
这篇文章复盘一次真实的踩坑:加字段后漏改 resultMap,导致查询结果缺字段,前端渲染空白。以及如何用建表语句反推完整的 Mapper,避免这种低级但高发的问题。
问题现场
表结构加了两个字段:
ALTER TABLE user ADD COLUMN email VARCHAR(100) DEFAULT NULL COMMENT '邮箱';
ALTER TABLE user ADD COLUMN avatar_url VARCHAR(255) DEFAULT NULL COMMENT '头像地址';
实体类补了字段,Controller 也改了,但前端页面邮箱和头像死活渲染不出来。打断点看返回的 User 对象,发现这两个字段是 null。
第一反应是 SQL 没查出来,把 MyBatis 日志级别调到 DEBUG,打印的 SQL 里明明有 SELECT id, name, email, avatar_url FROM user——字段都在。再往下追才发现:SQL 查了,但 MyBatis 没把结果映射到实体上。
根因:resultMap 只配了旧字段
打开 UserMapper.xml,resultMap 长这样:
<resultMap id="BaseResultMap" type="com.example.User">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="age" property="age" />
<!-- email 和 avatar_url 压根没配 -->
</resultMap>
MyBatis 的行为逻辑:
- resultMap 里配了的列 → 按映射关系填到实体
- resultMap 里没配的列 → 如果开启
map-underscore-to-camel-case,尝试自动映射;没开启就静默丢弃 - 没配的列 + 没开启驼峰映射 → 字段为 null,不报错、不警告
这次的问题就是:email 和 avatar_url 没在 resultMap 里,虽然开启了 map-underscore-to-camel-case,但 avatar_url 能自动映射成 avatarUrl,email 本来就能对上。可当时项目里自定义了一个 UserResultMap 用于关联查询,那个 resultMap 也没加新字段——查询走的是自定义 resultMap,自动映射兜底根本没生效。
手动补 resultMap 的坑
手动补 resultMap 看起来简单,实际操作一堆坑:
<resultMap id="BaseResultMap" type="com.example.User">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="age" property="age" />
<result column="email" property="email" />
<result column="avatar_url" property="avatarUrl" />
</resultMap>
坑 1:column 和 property 写反
<!-- 错误:column 是数据库字段,property 是 Java 字段 -->
<result column="avatarUrl" property="avatar_url" />
写反了不会报错,但查询结果永远是 null。
坑 2:jdbcType 类型不匹配
某些场景(如 Oracle 的 NUMBER 转 Java 的 Long vs BigDecimal)需要显式指定 jdbcType:
<result column="balance" property="balance" jdbcType="DECIMAL" />
不指定时 MyBatis 按默认规则猜,猜错了就是类型转换异常或精度丢失。
坑 3: association / collection 嵌套查询
一对多关联时,主表和子表字段混在一个 resultMap 里,新增字段到底加在 <result> 还是 <association> 里,位置错了整个嵌套结构解析失败。
用建表语句反推完整 Mapper
与其每次加字段后手动补 resultMap,不如直接用建表语句生成完整的 Mapper 文件,把「手写漏配」变成「生成后微调」。
把 CREATE TABLE 语句粘进 MyBatis Mapper 生成器,一键生成实体类、Mapper 接口和 XML(含 BaseResultMap、Base_Column_List、增删改查)。生成器按建表语句自动推导字段映射,不存在手写漏配的问题。
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT,
email VARCHAR(100),
avatar_url VARCHAR(255),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
工具生成的 resultMap 自动包含全部 6 个字段:
<resultMap id="BaseResultMap" type="com.example.User">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="age" property="age" />
<result column="email" property="email" />
<result column="avatar_url" property="avatarUrl" />
<result column="create_time" property="createTime" />
</resultMap>
关键动作:把工具生成的 resultMap 直接合并到项目里,替换手写的旧版本;只保留项目特有的自定义 resultMap(如关联查询),基础单表映射交给生成器。
防御性写法:让漏配当场报错
与其事后排查,不如让 MyBatis 在启动时就告诉你「resultMap 不完整」。
方案 1:开启 MyBatis 的自动映射检查
mybatis:
configuration:
map-underscore-to-camel-case: true
# 未映射的列尝试自动映射,但 resultMap 里显式配置的优先级更高
开启后,即使 resultMap 漏了字段,只要数据库列名和 Java 字段名符合驼峰转换规则,还能兜底。但注意:association / collection 嵌套里的字段不受此配置保护。
方案 2:代码里加字段数断言
在单元测试里断言查询结果的字段数:
@Test
void testUserQueryAllFields() {
User user = userMapper.selectById(1L);
assertNotNull(user.getEmail());
assertNotNull(user.getAvatarUrl());
// 新增字段后忘了改测试?编译就过不去
}
虽然麻烦,但新增字段时测试驱动强制检查,比上线后用户反馈空白页便宜得多。
方案 3:用 MyBatis-Plus 等增强框架
如果项目允许,MyBatis-Plus 的 @TableField 注解 + 通用 Mapper 可以省去手写 resultMap:
@TableName("user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
@TableField("avatar_url")
private String avatarUrl;
private LocalDateTime createTime;
}
字段映射靠注解完成,新增字段只需加注解,不存在 resultMap 漏配的问题。
常见坑
- 「SQL 里查了就行」:MyBatis 不是「SQL 查了就自动映射」,resultMap 是显式契约,漏配就是丢字段。
- 「驼峰映射开了就万事大吉」:
map-underscore-to-camel-case只对未在 resultMap 中显式配置的列生效,且嵌套查询的 association / collection 内部不适用。 - 「加了字段忘了改 resultMap」:表结构变更后,实体类、Mapper XML、前端字段三处必须同步,漏一处就出问题。
- 「Oracle NUMBER 类型默认映射成 BigDecimal」:如果实体类写的是 Long,查询会报类型转换异常,需要在 resultMap 里显式指定
jdbcType="DECIMAL"或把实体字段改成 BigDecimal。
相关工具与速查
- MyBatis Mapper 生成器:粘贴建表语句一键生成实体类、Mapper 接口与 XML(含 BaseResultMap、增删改查),支持 MySQL / PostgreSQL / Oracle / SQL Server / SQLite 五种方言
- SQL 转 Java 实体工具:只需要实体类时使用,支持 Lombok、Swagger 注解
- SQL 通用语法速查:SELECT、JOIN、子查询、分页等基础语法一页速查
- MySQL 命令速查:DDL、DML、索引、事务等 MySQL 高频命令