最后更新:2026-10-28
适用场景:MyBatis 3、Spring Boot 3.x、XML Mapper / 注解 Mapper

SQL 在数据库客户端里能查到值,Mapper 返回的对象也不是 null,但 userName、createdAt 等字段偏偏是 null。这时先别动数据库连接配置。查不到行和查到行却没有映射进属性,要走两条不同的排查路线。

先把 SQL 结果和 Java 对象分开看

用实际参数在同一个数据库、同一个 schema 执行 Mapper 里的 SQL。记录返回行数、列名和列值。不要只看 select * 的截图:项目里可能有同名表、视图或不同环境的数据。然后在 Mapper 调用后立即检查返回对象,排除后续 DTO 转换把值丢掉。

例如数据库列是 user_name,Java 属性是 userName:

public class UserRow {
    private Long id;
    private String userName;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getUserName() { return userName; }
    public void setUserName(String userName) { this.userName = userName; }
}

如果 SQL 实际列名与属性名不匹配,自动映射不会凭语义猜出对应关系。MyBatis 官方文档说明,自动映射会按列名寻找属性;下划线转驼峰需要显式启用。MyBatis 结果映射文档

先试明确别名,再考虑全局设置

只影响一条查询时,给列一个与属性一致的别名最直观:

<select id="findById" resultType="com.example.UserRow">
  select id, user_name as userName
  from app_user
  where id = #{id}
</select>

如果项目的表字段普遍用下划线,也可以在 MyBatis Spring Boot Starter 的配置里启用:

mybatis:
  configuration:
    map-underscore-to-camel-case: true

它影响自动映射范围内的所有查询,改前要检查已有别名和结果映射,避免把局部问题变成全局行为变化。Starter 官方配置示例给出了该属性。resultType 适合简单对象;需要逐列控制、关联对象或复杂结构时,用 resultMap 写明确对应关系。

<resultMap id="userRowMap" type="com.example.UserRow">
  <id column="id" property="id"/>
  <result column="user_name" property="userName"/>
</resultMap>

别只复制这段 resultMap:<select> 还得写 resultMap="userRowMap",否则它不会被使用。resultType 与 resultMap 不要在同一个查询上混用。

列名对了,仍是 null

先看 SQL 是否给同一列起了重复别名,尤其是多表 join 里两个表都有 id、name。给每个要映射的列起唯一且稳定的别名,再检查属性是否可写:普通 JavaBean 的 setter 名称、字段类型、构造器方式是否与结果类型匹配。record 或只有全参构造器的不可变对象,不应按普通可变 JavaBean 的思路排查。

还要分清数据库值本来就是 NULL、查询条件没有命中、对象创建成功但某个字段没映射。必要时把一条 SQL 缩成只选 id, user_name 的最小查询,排除复杂联表与类型处理器。若值是 JSON、枚举或自定义类型,继续检查 TypeHandler,而不是只盯着驼峰配置。

如果 Mapper 语句根本没找到,通常会报 Invalid bound statement,不是“字段为 null”;两者排查入口不同,可对照MyBatis Invalid bound statement 怎么处理。

参考资料

MyBatis 3:Mapper XML Files / Result Maps
MyBatis 3:Configuration
MyBatis Spring Boot Starter:Configuration

更新记录

2026-10-28:创建文章,补充列别名、驼峰配置与 resultMap 分层排查。