Mybatis 查询为空行时返回对象

2025-06-16, 星期一, 19:33

赛博驱鬼故事会

上线了一个数据导入功能,写入时需要通过外键查出额外数据补充到当前行(当然这里表设计是否合理暂且不论)。

List<E> es = repository.list(Wrappers.lambdaQuery(E.class)
        .eq(E::getTID, row.getTID())
        .select(E::getName));
row.setName(es.get(0).getName());
// ...

在这里 E.tid 逻辑上具有唯一性,而如果找不到符合条件的记录 Ex,则不导入该行。于是编写如下判断语句:

if (es.isEmpty()) {
    // throw error message
} else if (es.size() > 1) {
    // throw error message
}
// do sth.

然而程序在执行时却抛出了 NPE,理由是 List.get(index) 返回 null。从数据侧溯源,原来是对应的 Ex 记录中 name 为空。不过写多了 Java Stream,笔者的第一反应觉得这里应该是 List.of(new E(name=null))

查看 Mybatis 3.5.19 的源码,在装配结果的方法 org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, java.lang.String) 中含有如下语句:

Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    // ...
    rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;

Mybatis 先创建了对象实例,当查询没有任何列值可以映射到对象,且没有设置 ReturnInstanceForEmptyRow 时,转而返回 null

虽然 Mybatis 在 3.4.2 之前的行为都是「为空行返回 null」,在笔者的这个场景中要分别判断是否存在特定行和指定列是否为空还是有点繁琐了。解决办法有两个,一是为 select() 语句添加 NotNull 的属性,例如主键什么的。

第二种是在 Mybatis 中打开 ReturnInstanceForEmptyRow 开关(Mybatis 3.4.2+)。Mybatis-Plus 也提供了这一配置的映射,通过 mybatis-plus.configuration.return-instance-for-empty-row=true 启用。