排查过最窝火的一类问题: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 高频命令