MyBatis 查询结果字段为 null?一文讲透下划线命名与驼峰映射问题
前言
在使用 MyBatis 进行数据库查询时,你是否遇到过这样的情况:明明 SQL 执行成功了,数据也查出来了,但返回的 Java 对象中,某些字段却是 null?本文将从一个真实案例出发,深入剖析这个问题的根本原因,并给出三种解决方案,帮助你彻底搞懂 MyBatis 的结果映射机制。
一、问题场景
1.1 数据库表结构
假设我们有一张汽车表 t_car,表结构如下:
sql
CREATE TABLE t_car (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
car_num VARCHAR(255), -- 车牌号
brand VARCHAR(255), -- 品牌
guide_price DECIMAL(10,2), -- 指导价
produce_time CHAR(10), -- 生产日期
car_type VARCHAR(255) -- 车辆类型
);
表中的数据如下:
| id | car_num | brand | guide_price | produce_time | car_type |
|---|---|---|---|---|---|
| 1 | 京A88888 | 宝驹535 | 20.00 | 2025-10-11 | 燃油车 |
| 2 | 京A66666 | 劳斯莱斯泡影 | 300.00 | 2025-10-12 | 新能源 |
| 3 | 海A8888 | 宝马 | 30.00 | 2023-02-02 | 燃油车 |
| ... | ... | ... | ... | ... | ... |
1.2 Java 实体类
对应的 Java 实体类 Car.java:
java
public class Car {
private Long id;
private String carNum; // 对应数据库 car_num
private String brand;
private Double guidePrice; // 对应数据库 guide_price
private String produceTime; // 对应数据库 produce_time
private String carType; // 对应数据库 car_type
// getter / setter / toString
}
1.3 MyBatis Mapper 配置
Mapper 接口:
java
public interface CarMapper {
List<Car> selectAll();
}
Mapper XML 映射文件:
xml
<select id="selectAll" resultType="car">
SELECT * FROM t_car
</select>
1.4 查询结果异常
执行查询后,得到的结果如下:
ini
Car{id=3, carNum='null', brand='宝马', guidePrice=null, produceTime='null', carType='null'}
Car{id=4, carNum='null', brand='宝马', guidePrice=null, produceTime='null', carType='null'}
Car{id=5, carNum='null', brand='宝马', guidePrice=null, produceTime='null', carType='null'}
...
问题 :id 和 brand 正常映射,但 carNum、guidePrice、produceTime、carType 均为 null。
这到底是为什么?
二、原因分析
2.1 根本原因:列名与属性名不匹配
观察表字段和 Java 属性的对应关系:
| 数据库列名(下划线) | Java 属性名(驼峰) | 是否一致 |
|---|---|---|
id |
id |
✅ 一致 |
brand |
brand |
✅ 一致 |
car_num |
carNum |
❌ 不一致 |
guide_price |
guidePrice |
❌ 不一致 |
produce_time |
produceTime |
❌ 不一致 |
car_type |
carType |
❌ 不一致 |
在 MyBatis 中,当使用 resultType 进行自动映射 时,默认要求 数据库列名与 Java 属性名完全一致(大小写敏感)。
- 对于
id和brand,列名和属性名完全匹配 → ✅ 映射成功 - 对于
car_num等字段,列名和属性名不匹配 → ❌ 映射失败,属性保持null
这就是为什么部分字段能映射成功,而其他字段却为 null 的根本原因。
2.2 为什么没有报错?
MyBatis 的自动映射是宽容 的。对于无法匹配的列,它不会抛出异常,而是忽略 该列的映射,Java 对象中的对应属性会保持默认值(引用类型为 null,基本类型为默认值)。这就是为什么你能看到 carNum=null、guidePrice=null 的结果。
三、解决方案(三选一)
方案一:开启驼峰命名自动转换 ⭐ 推荐
MyBatis 提供了一个全局开关,可以将数据库的下划线命名 自动映射为 Java 的驼峰命名。
在 mybatis-config.xml 中添加:
xml
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
开启后,MyBatis 会自动完成以下映射:
car_num→carNumguide_price→guidePriceproduce_time→produceTimecar_type→carType
优点:
- 配置一次,全局生效。
- 所有 Mapper 都不需要额外修改。
- 代码简洁,维护成本最低。
适用场景 :所有项目都推荐开启,这是 MyBatis 官方推荐的最佳实践之一。
方案二:使用 resultMap 手动映射
如果不想开启全局设置(例如项目中有特殊需求),可以使用 resultMap 手动指定列与属性的映射关系:
xml
<resultMap id="carMap" type="car">
<id property="id" column="id"/>
<result property="carNum" column="car_num"/>
<result property="brand" column="brand"/>
<result property="guidePrice" column="guide_price"/>
<result property="produceTime" column="produce_time"/>
<result property="carType" column="car_type"/>
</resultMap>
<select id="selectAll" resultMap="carMap">
SELECT * FROM t_car
</select>
注意 :使用 resultMap 时,<select> 标签中必须用 resultMap 属性,而不是 resultType。
优点:
- 映射关系清晰可见。
- 支持复杂场景(一对一、一对多关联映射)。
缺点:
- 每个实体类都需要配置一个
resultMap,工作量较大。 - 字段较多时配置繁琐。
方案三:SQL 中使用别名
在 SQL 查询中直接为列起别名,让别名与 Java 属性名一致:
xml
<select id="selectAll" resultType="car">
SELECT
id,
car_num AS carNum,
brand,
guide_price AS guidePrice,
produce_time AS produceTime,
car_type AS carType
FROM t_car
</select>
优点:
- 无需修改全局配置。
- 单个查询可独立控制映射。
缺点:
- 每个查询都需要写别名,工作量大。
- 容易遗漏,维护成本高。
- 违背 DRY(Don't Repeat Yourself)原则。
四、方案对比与选择建议
| 方案 | 配置成本 | 维护成本 | 适用场景 | 推荐度 |
|---|---|---|---|---|
| 开启驼峰转换 | 一次配置 | 低 | 所有项目 | ⭐⭐⭐⭐⭐ |
resultMap 手动映射 |
每个实体一次 | 中 | 复杂映射、关联查询 | ⭐⭐⭐⭐ |
| SQL 别名 | 每个查询一次 | 高 | 临时查询、特殊需求 | ⭐⭐ |
强烈建议 :所有 MyBatis 项目都应该开启 mapUnderscoreToCamelCase,这是最简单、最优雅的解决方案。
五、问题排查步骤
当遇到查询结果字段为 null 时,可以按以下步骤逐一排查:
第 1 步:检查实体类属性名与数据库列名是否一致
确认列名与属性名的对应关系,特别关注下划线命名和驼峰命名的差异。
第 2 步:检查是否开启了驼峰转换
在 mybatis-config.xml 中查看是否有以下配置:
xml
<setting name="mapUnderscoreToCamelCase" value="true"/>
第 3 步:检查是否使用了 resultMap 且配置完整
如果使用了 resultMap,确认所有需要的列都已配置 <result> 或 <id>。
第 4 步:开启 MyBatis 日志
在 mybatis-config.xml 中添加:
xml
<setting name="logImpl" value="STDOUT_LOGGING"/>
查看控制台输出的 SQL 和参数,确认查询语句是否正确,列名是否符合预期。
第 5 步:检查 SQL 是否 SELECT 了所有需要的列
确认 SQL 语句中包含了所有需要映射的列。如果 SELECT 列表漏掉了某些列,它们在 Java 对象中会保持默认值(null)。
六、深度扩展:为什么 id 和 brand 能正常映射?
在这个案例中,id 和 brand 正常映射,而其他字段却为 null,原因很直观:
| 数据库列名 | Java 属性名 | 是否匹配 |
|---|---|---|
id |
id |
✅ 完全一致 |
brand |
brand |
✅ 完全一致 |
car_num |
carNum |
❌ 不一致(有下划线) |
MyBatis 的自动映射是基于名称的。完全匹配的能映射,不完全匹配的(如包含下划线)在不开启驼峰转换的情况下,无法映射。
七、最佳实践总结
- 统一命名规范:数据库使用下划线命名,Java 使用驼峰命名,这是业界标准做法。
- 始终开启驼峰转换 :在
mybatis-config.xml中配置mapUnderscoreToCamelCase=true,这是 MyBatis 的标配。 - 复杂映射使用
resultMap:当关联查询(一对一、一对多)或需要自定义映射规则时,使用resultMap。 - 开启日志调试 :开发环境下开启
STDOUT_LOGGING,直观查看 SQL 执行情况。 - 单元测试验证:对 Mapper 方法编写单元测试,确保映射逻辑正确。
八、完整配置示例
最终的 mybatis-config.xml:
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 开启驼峰命名转换(核心) -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启日志 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<package name="com.xie.entity"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.xie.mapper"/>
</mappers>
</configuration>
开启驼峰转换后,查询结果将正常显示:
ini
Car{id=3, carNum='海A8888', brand='宝马', guidePrice=30.0, produceTime='2023-02-02', carType='燃油车'}
Car{id=4, carNum='海A8888', brand='宝马', guidePrice=30.0, produceTime='2023-02-02', carType='燃油车'}
...
结语
MyBatis 查询结果字段为 null 的问题,90% 以上的情况都是由于列名与属性名不匹配 造成的。通过开启 mapUnderscoreToCamelCase 这个开关,你可以一劳永逸地解决下划线命名与驼峰命名的映射问题,让数据访问层的代码更加简洁、可靠。
希望本文能帮助你在 MyBatis 的使用过程中少踩坑,写出更优雅的代码!