MyBatis Mapper 查询无数据时返回什么?Java 代码中是否需要非空判断?
·
在 MyBatis 项目中,一个常见问题是:当数据库查询没有匹配记录时,Mapper 方法返回什么? 是 null 还是空集合/空 Map?Java 代码中是否需要进行非空判断?
本文基于 MyBatis 3.5.x 系列(截至 2025 年 12 月行为一致)源码,详细分析单个对象、集合(List)、Map 三种常见返回类型的行为,并给出实践建议。
1. 核心结论
MyBatis 返回行为取决于 Mapper 方法的返回类型:
| 返回类型 | 无记录时返回 | 有记录时返回 | 多条记录异常情况 | 是否需要 null 判断 |
|---|---|---|---|---|
| 单个对象(如 User) | null | 对应的对象实例 | 抛 TooManyResultsException | 必须 |
| List | 空 List(size()==0) | 包含元素的 List | 无异常 | 不需要 |
| Map<K,V> | 空 Map(size()==0) | 包含键值对的 Map | 无异常 | 不需要 |
Java 代码实践:
- List 和 Map:无需 null 判断,可以直接调用 isEmpty()、size()、forEach 等方法,绝对安全,不会 NPE。
- 单个对象:必须进行 null 判断。
MyBatis 的集合和 Map 返回设计遵循 Java 最佳实践:返回空容器而非 null,极大减少 NPE 风险。
2. 源码深度分析
查询链路:Mapper 接口(动态代理) → DefaultSqlSession → Executor → DefaultResultSetHandler。
2.1 所有查询底层:selectList() → handleResultSets()
// DefaultSqlSession.selectList()
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
最终结果处理在 DefaultResultSetHandler.handleResultSets():
public List<Object> handleResultSets(Statement stmt) throws SQLException {
final List<Object> multipleResults = new ArrayList<>(); // 初始化空 List
// 获取第一个 ResultSet
ResultSetWrapper rsw = getFirstResultSet(stmt);
if (resultHandler == null) { // 普通查询,resultHandler 为 null
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList()); // 添加结果(即使为空 List)
}
// 处理多 ResultSet 情况...
return collapseSingleResultList(multipleResults); // 始终返回 List(非 null)
}
- 关键点:即使 ResultSet 没有一行数据(next() 为 false),DefaultResultHandler.getResultList() 仍返回 new ArrayList<>()(空 List)。
- 对于 Map 返回类型,MyBatis 会使用 DefaultMapResultHandler(继承自 DefaultResultHandler),同样返回 new LinkedHashMap<>()(空 Map)。
2.2 selectOne() 的特殊处理(单个对象)
public <T> T selectOne(String statement, Object parameter) {
List<T> list = selectList(statement, parameter); // 复用 selectList
if (list.size() == 0) {
return null; // 明确返回 null
} else if (list.size() > 1) {
throw new TooManyResultsException(...);
}
return list.get(0);
}
关键点:selectOne内部也是复用 selectList,且没有对list进行非空判断。
2.3 Map 返回的处理(DefaultMapResultHandler)
public class DefaultMapResultHandler<K, V> implements ResultHandler<V> {
private final Map<K, V> map;
public DefaultMapResultHandler() {
this.map = new LinkedHashMap<>(); // 初始化空 Map
}
// ... 处理每一行
public Map<K, V> getMappedResults() {
return map; // 即使无行,也返回空 Map
}
}
3. 代码示例
public interface UserMapper {
User getById(Long id); // 单个对象
List<User> listByStatus(Integer status); // List
Map<Long, User> mapByIdIn(@Param("ids") List<Long> ids); // Map<Long, User>
}
// 单个对象
User user = mapper.getById(999L); // 无记录 → null
if (user != null) { // 必须判断
System.out.println(user.getName());
}
// List
List<User> users = mapper.listByStatus(999); // 无记录 → 空 List
users.forEach(u -> ...); // 安全
if (users.isEmpty()) {
System.out.println("无数据");
}
// Map
Map<Long, User> userMap = mapper.mapByIdIn(emptyIdList); // 无记录 → 空 Map
if (userMap.isEmpty()) {
System.out.println("无数据");
}
userMap.forEach((id, u) -> ...); // 安全迭代
4. 最佳实践与注意事项
- 使用集合/Map 查询无需非空判断:如果没有查询到数据,MyBatis 会返回一个空的集合(
List、Set等),不需要进行非空判断,只需要判断集合是否为空。 - 单个对象场景:查询唯一键(如主键)时可用,但务必加 null 检查。
- 插件风险:自定义插件或 TypeHandler 可能改变返回行为,标准 MyBatis 保持上述规则。
5. 总结
MyBatis 在无记录时的返回行为清晰且安全:
- List / Map:返回空容器(无需 null 判断)。
- 单个对象:返回 null(必须判断)。
深入查看 DefaultResultSetHandler.java、DefaultMapResultHandler.java 和 DefaultSqlSession.java 源码,你会发现 MyBatis 在容器处理上非常严谨,始终避免返回 null。
掌握这些规则,能让你写出更少 bug、更易维护的持久层代码。
有特定返回类型或插件场景疑问,欢迎继续讨论!
更多推荐


所有评论(0)