MyBatis 的 ResultMap 到底还需不需要写了很多年 MyBatis最近排查了一个字段莫名为 null 的问题顺便把 ResultMap 相关的几个坑梳理了一下做个沉淀。先说结论大多数情况下resultType就够用了resultMap往往是冗余的。但有几种情况确实需要它。下面一个一个说。一、先搞清楚一个配置mapUnderscoreToCamelCase很多人没注意过这个配置但它直接决定了你用resultType还是resultMap。!-- MyBatis 全局配置 --configurationsettingssettingnamemapUnderscoreToCamelCasevaluetrue//settings/configuration开启之后MyBatis 会自动帮你把数据库的下划线列名转成驼峰属性名数据库列名Java 属性名user_iduserIdcreate_timecreateTimeorder_statusorderStatus没开启的话user_id就是user_id找不到对应的驼峰属性字段就是 null。如果你的项目是代码生成器生成的先检查一下这个配置有没有开启这决定了后面的结论是否成立。二、ResultMap 在多数场景下是冗余的MyBatis GeneratorMBG生成的 Mapper XML 都会带一个BaseResultMapresultMapidBaseResultMaptypecom.example.Useridcolumnidpropertyid/resultcolumnuser_namepropertyuserName/resultcolumncreate_timepropertycreateTime//resultMapselectidselectByPrimaryKeyresultMapBaseResultMapselect id, user_name, create_time from t_user where id #{id}/select如果已经开启了mapUnderscoreToCamelCasetrue上面这个 resultMap 里声明的每一条映射和 MyBatis 自动映射的结果完全一样。换句话说直接这样写也完全没问题selectidselectByPrimaryKeyresultTypecom.example.Userselect id, user_name, create_time from t_user where id #{id}/select代码更简洁维护成本更低。三、什么时候必须用 ResultMap场景 1多表 JOIN列名冲突-- 两张表都有 user_name不区分会冲突SELECTu.user_name,o.user_nameasorder_user_nameFROMt_user uJOINt_order oONu.ido.user_id这种情况需要用 resultMap 明确指定每个列对应哪个属性resultMapidUserOrderMaptypecom.example.UserOrderVOresultcolumnuser_namepropertyuserName/resultcolumnorder_user_namepropertyorderUserName//resultMap场景 2嵌套对象映射Java 对象里套了另一个对象MyBatis 不知道怎么自动拆publicclassOrder{privateLongid;privateUsercreateUser;// 嵌套对象}需要association标签显式声明这就必须用 resultMap。场景 3自定义类型转换数据库存的是 JSON 字符串Java 里要转成对象需要 TypeHandlerresultcolumnextra_infopropertyextraInfotypeHandlercom.example.JsonTypeHandler/这种场景 resultMap 是刚需。四、真正的坑SQL 别名与 ResultMap 对不上这是最容易踩的坑也是我今天排查的问题根因。问题复现假设 resultMap 声明了原始列名的映射resultMapidBaseResultMaptypecom.example.Userresultcolumnuser_namepropertyuserName//resultMap某天为了可读性在 SQL 里加了别名selectidselectUserresultMapBaseResultMapselect user_name as userName from t_user/select结果一查userName属性是null。为什么会 nullMyBatis 用结果集返回的列名去匹配 resultMap 的column结果集列名别名userName resultMap 中声明的user_name ↑ 找不到匹配失败 → 字段为 null用了别名结果集里的列名就变成了userName而 resultMap 还在找user_name自然对不上。怎么解决最简单的办法换成 resultTypeselectidselectUserresultTypecom.example.Userselect user_name as userName from t_user/selectresultType不依赖 resultMap直接用列名匹配 Java 属性名userName直接对上userName属性问题解决。五、一个简单的判断流程SQL 有没有用别名 ├── 没有 → resultType 就够了前提是开了驼峰转换 └── 有 → 别名和 Java 属性名一致吗 ├── 一致 → resultType 更简洁 └── 不一致 → 要么改别名要么用 resultMap 有没有复杂映射嵌套对象 / TypeHandler / 列名冲突 ├── 没有 → resultType └── 有 → resultMap 是刚需六、几条实用建议1. 先确认mapUnderscoreToCamelCase有没有开没开的话下划线列名全部映射失败所有字段都是 null。这是新人最常遇到的字段为什么是 null问题。2. 代码生成器的 resultMap 可以删在开启了驼峰转换的项目里Generator 生成的 resultMap 是冗余的用resultType替代XML 会清爽很多。3. 用了别名就不要再依赖 resultMap别名会改变结果集的列名和 resultMap 的 column 声明容易脱节。要么全用 resultType要么别名和 resultMap 同步维护。4. resultMap 是防御性写法resultType 是简洁性写法没有对错看场景。简单查询用 resultType复杂场景上 resultMap。写在最后写这篇文章的初衷是很多团队里 MyBatis 的 XML 都是从 Generator 原封不动搬过来的resultMap写了很久但没人真正理解它的作用也不知道mapUnderscoreToCamelCase这个开关的存在。了解了原理该删的删该留的留代码反而更清晰。2026-08-03