Mybatis查询结果为空时,为什么返回值为NULL或空集合?
阅读本文大概需要 11 分钟。
来自:https://c1n.cn/6l7NH
背景
开始前我们先看一个问题:
JDBC 中的 ResultSet 简介
while(rs.next()){
// 获取数据
int id = rs.getInt(1);
String name = rs.getString("name");
System.out.println(id + "---" + name);
}
结果集处理入口 ResultSetHandler
public interface ResultSetHandler {
// 将ResultSet映射成Java对象
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
// 将ResultSet映射成游标对象
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
// 处理存储过程的输出参数
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
| handleResultSets
public List<Object> handleResultSets(Statement stmt) throws SQLException {
// 用于记录每个ResultSet映射出来的Java对象
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 从Statement中获取第一个ResultSet,其中对不同的数据库有兼容处理逻辑,
// 这里拿到的ResultSet会被封装成ResultSetWrapper对象返回
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 获取这条SQL语句关联的全部ResultMap规则。如果一条SQL语句能够产生多个ResultSet,
// 那么在编写Mapper.xml映射文件的时候,我们可以在SQL标签的resultMap属性中配置多个
// <resultMap>标签的id,它们之间通过","分隔,实现对多个结果集的映射
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) { // 遍历ResultMap集合
ResultMap resultMap = resultMaps.get(resultSetCount);
// 根据ResultMap中定义的映射规则处理ResultSet,并将映射得到的Java对象添加到
// multipleResults集合中保存
handleResultSet(rsw, resultMap, multipleResults, null);
// 获取下一个ResultSet
rsw = getNextResultSet(stmt);
// 清理nestedResultObjects集合,这个集合是用来存储中间数据的
cleanUpAfterHandlingResultSet();
resultSetCount++; // 递增ResultSet编号
}
// 下面这段逻辑是根据ResultSet的名称处理嵌套映射,你可以暂时不关注这段代码,
// 嵌套映射会在后面详细介绍
...
// 返回全部映射得到的Java对象
return collapseSingleResultList(multipleResults);
}
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
// 将该ResultSet结果集处理完后的List对象放入multipleResults中,这样就可以支持返回多个结果集了
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
| DefaultResultHandler 和 DefaultResultContext
DefaultResultHandler 实现的底层使用 ArrayList<Object> 存储单个结果集映射得到的 Java 对象列表。
DefaultMapResultHandler 实现的底层使用 Map<K, V> 存储映射得到的 Java 对象,其中 Key 是从结果对象中获取的指定属性的值,Value 就是映射得到的 Java 对象。
| 多结果集返回
private List<Object> collapseSingleResultList(List<Object> multipleResults) {
// 如果只有一个结果集就返回一个,否则直接通过List列表返回多个结果集
return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
}
简单映射
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) { // 包含嵌套映射的处理流程
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else { // 简单映射的处理
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
// 跳过多余的记录
skipRows(resultSet, rowBounds);
// 检测是否还有需要映射的数据
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// 处理映射中用到的 Discriminator,决定此次映射实际使用的 ResultMap。
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
执行 skipRows() 方法跳过多余的记录,定位到指定的行。
通过 shouldProcessMoreRows() 方法,检测是否还有需要映射的数据记录。
如果存在需要映射的记录,则先通过 resolveDiscriminatedResultMap() 方法处理映射中用到的 Discriminator,决定此次映射实际使用的 ResultMap。
通过 getRowValue() 方法对 ResultSet 中的一行记录进行映射,映射规则使用的就是步骤 3 中确定的 ResultMap。
执行 storeObject() 方法记录步骤 4 中返回的、映射好的 Java 对象。
| ResultSet 的预处理
| 确定 ResultMap
public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException {
// 用于维护处理过的ResultMap唯一标识
Set<String> pastDiscriminators = new HashSet<>();
// 获取ResultMap中的Discriminator对象,这是通过<resultMap>标签中的<discriminator>标签解析得到的
Discriminator discriminator = resultMap.getDiscriminator();
while (discriminator != null) {
// 获取当前待映射的记录中Discriminator要检测的列的值
final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
// 根据上述列值确定要使用的ResultMap的唯一标识
final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
if (configuration.hasResultMap(discriminatedMapId)) {
// 从全局配置对象Configuration中获取ResultMap对象
resultMap = configuration.getResultMap(discriminatedMapId);
// 记录当前Discriminator对象
Discriminator lastDiscriminator = discriminator;
// 获取ResultMap对象中的Discriminator
discriminator = resultMap.getDiscriminator();
// 检测Discriminator是否出现了环形引用
if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
break;
}
} else {
break;
}
}
// 返回最终要使用的ResultMap
return resultMap;
}
| 创建映射结果对象
首先根据 ResultMap 的 type 属性值创建映射的结果对象。
然后根据 ResultMap 的配置以及全局信息,决定是否自动映射 ResultMap 中未明确映射的列。
接着根据 ResultMap 映射规则,将 ResultSet 中的列值与结果对象中的属性值进行映射。
最后返回映射的结果对象,如果没有映射任何属性,则需要根据全局配置决定如何返回这个结果值,这里不同场景和配置,可能返回完整的结果对象、空结果对象或是 null。
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 根据ResultMap的type属性值创建映射的结果对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 根据ResultMap的配置以及全局信息,决定是否自动映射ResultMap中未明确映射的列
if (shouldApplyAutomaticMappings(resultMap, false)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 根据ResultMap映射规则,将ResultSet中的列值与结果对象中的属性值进行映射
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
// 如果没有映射任何属性,需要根据全局配置决定如何返回这个结果值,
// 这里不同场景和配置,可能返回完整的结果对象、空结果对象或是null
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
| 自动映射
检测当前使用的 ResultMap 是否配置了 autoMapping 属性,如果是,则直接根据该 autoMapping 属性的值决定是否开启自动映射功能。
检测 mybatis-config.xml 的 <settings> 标签中配置的 autoMappingBehavior 值,决定是否开启自动映射功能。NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段;FULL 会自动映射任何复杂的结果集(无论是否嵌套)。
| 正常映射
| 存储对象
private void storeObject(...) throws SQLException {
if (parentMapping != null) {
// 嵌套查询或嵌套映射的场景,此时需要将结果对象保存到外层对象对应的属性中
linkToParents(rs, parentMapping, rowValue);
} else {
// 普通映射(没有嵌套映射)或是嵌套映射中的外层映射的场景,此时需要将结果对象保存到ResultHandler中
callResultHandler(resultHandler, resultContext, rowValue);
}
}
回归最初的问题:查询结果为空时的返回值
| 返回结果为单行数据
// 检测是否还有需要映射的数据
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next())
public class DefaultResultHandler implements ResultHandler<Object> {
// 默认是空集合
private final List<Object> list;
public DefaultResultHandler() {
list = new ArrayList<>();
}
@SuppressWarnings("unchecked")
public DefaultResultHandler(ObjectFactory objectFactory) {
list = objectFactory.create(List.class);
}
@Override
public void handleResult(ResultContext<? extends Object> context) {
list.add(context.getResultObject());
}
public List<Object> getResultList() {
return list;
}
}
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
| 返回结果为多行数据
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
结论
推荐阅读:
互联网初中高级大厂面试题(9个G) 内容包含Java基础、JavaWeb、MySQL性能优化、JVM、锁、百万并发、消息队列、高性能缓存、反射、Spring全家桶原理、微服务、Zookeeper......等技术栈!
⬇戳阅读原文领取! 朕已阅