MyBatis中ResultSetHandler的具体使用
作者:灵魂猎手
StatementHandler
参数处理&结果处理,分别委托给ParameterHandler
&ResultSetHandler
(注意不是ResultHandler
)。上一篇文章介绍了ParameterHandler
,本文介绍ResultSetHandler
。
一、ResultSetHandler接口
我们先看下ResultSetHandler
接口定义:
public interface ResultSetHandler { // 处理结果集,返回结果列表 <E> List<E> handleResultSets(Statement stmt) throws SQLException; // 处理游标结果集,返回Cursor对象(用于流式处理) <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException; // 处理存储过程的输出参数 void handleOutputParameters(CallableStatement cs) throws SQLException; }
可以看到,ResultSetHandler
接口定义了三种不同场景下,处理JDBC结果集的方法。其核心职责是将数据库返回的ResultSet
转换为Java对象。
handleResultSets
:核心方法,处理JDBC的Statement
的结果集,注意普通查询和存储过程查询,都使用这个方法的结果集。handleCursorResultSets
:处理Mapper的返回值是Cursor
的方法。handleOutputParameters
:处理存储过程的输出参数,存储过程会使用handleResultSets
处理结果集,使用本方法,处理输出类型的参数。
二、DefaultResultSetHandler 的核心实现
MyBatis仅提供一个默认实现类DefaultResultSetHandler
,我们这里分析下核心的handleResultSets
方法源码。
1. 整体流程:handleResultSets 方法
handleResultSets
是处理查询结果集的核心方法,负责把Statement
的结果集转换为Java对象,需要注意,无论是普通的查询还是存储过程查询,都是调用这个方法。handleOutputParameters
只是处理存储过程的参数的(处理参数类型为OUT/INOUT类型的)。
注意这个方法名字,handleResultSets,有个s,暗示了结果集可能是多个
@Override public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; // 获取第一个结果集 ResultSetWrapper rsw = getFirstResultSet(stmt); // 获取MappedStatement中定义的结果集映射(可能有多个,对应多结果集) List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); // 第一部分:处理 MappedStatement 中定义的 resultMaps 对应的结果集 while (rsw != null && resultSetCount < resultMaps.size()) { ResultMap resultMap = resultMaps.get(resultSetCount); // 处理当前结果集,映射为Java对象 handleResultSet(rsw, resultMap, multipleResults, null); // 获取下一个结果集(用于存储过程多结果集场景) rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } // 第二部分:处理MappedStatement中resultSets指定的附加结果集(用于解决N+1) String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); // 解决N+1,向父对象设置属性。 if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, multipleResults, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } // 合并结果(如果只有一个结果集,直接返回该列表) return collapseSingleResultList(multipleResults); }
核心流程分为两部分
- 处理MappedStatement中定义的 resultMaps 对应的结果集。
- 处理MappedStatement中resultSets指定的附加结果集,用于解决N+1问题。
两部分都可能是多个!!!
原来,Mybatis可以支持一次获取多个结果集,很神奇。那么如何返回多个结果集呢?
- Mybatis 如何返回多个结果集
- 也可以通过存储过程,这个官网有例子XML 映射器-结果映射(搜索ResultSets,可以解决N+1问题,这是第二个循环的来源)。
2. 单结果集处理:handleResultSet 方法
handleResultSet
负责处理单个结果集,根据是否有自定义 ResultHandler
选择不同的处理逻辑:
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) { // 无自定义ResultHandler时,使用默认的DefaultResultHandler收集结果 DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); multipleResults.add(defaultResultHandler.getResultList()); } else { // 有自定义ResultHandler时,使用其处理结果 handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { // 关闭结果集 closeResultSet(rsw.getResultSet()); } }
3. 行记录映射:handleRowValues 与 getRowValue 方法
无论有无自定义ResultHandler
,都是调用handleRowValues
方法:
// 处理结果集中的行记录 public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { if (resultMap.hasNestedResultMaps()) { // 处理嵌套结果映射(如包含association/collection) 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<>(); // 跳过RowBounds指定的偏移量,内存分页 skipRows(rsw.getResultSet(), rowBounds); // 循环处理每行记录,直到达到RowBounds的限制或结果集结束 while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { // 解析鉴别器(discriminator),动态选择ResultMap ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); // 将当前行映射为Java对象 Object rowValue = getRowValue(rsw, discriminatedResultMap); // 存储对象(调用ResultHandler处理) storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } }
Q:discriminator是什么鬼东西??
A:动态识别返回对象类型,比如我们定义了一个动物类,查询结果需要动态转为阿猫阿狗,就可以使用这个特性,可以在官网中查看使用方法:XML 映射器-结果映射(搜索discriminator)。
接下来看下核心的getRowValue
方法。
// 将单行记录映射为Java对象 private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); // 创建结果对象(通过ObjectFactory) Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null); if (rowValue != null && !typeHandlerRegistry.hasTypeHandler(rowValue.getClass())) { // 获取结果对象的元信息(用于反射设置属性) final MetaObject metaObject = configuration.newMetaObject(rowValue); boolean foundValues = this.useConstructorMappings; // 处理结果映射(ResultMapping),设置对象属性 if (shouldApplyAutomaticMappings(resultMap, false)) { // 自动映射(未在ResultMap中显式配置的字段) foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues; } // 显式映射(ResultMap中配置的result/association/collection) foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } return rowValue; }
getRowValue
方法的核心逻辑:
- 创建结果对象:通过
ObjectFactory
实例化目标对象(如User
)。 - 自动映射:对
ResultMap
中未显式配置的字段,根据列名与属性名的匹配关系自动映射。 - 显式映射:根据
ResultMap
中配置的result
、association
、collection
等标签,通过TypeHandler
将ResultSet
中的列值转换为对象属性。
4. applyAutomaticMappings
除了使用ResultMap标签进行映射,Mybatis还支持自动映射,也就是通过蛇形转驼峰的方式,将数据库字段,映射成Java属性:
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { // 1. 创建自动映射列表:筛选出未显式配置但存在匹配的字段 List<UnMappedColumnAutoMapping> autoMappings = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix); if (autoMappings.isEmpty()) { return false; } boolean foundValues = false; // 2. 遍历自动映射列表,逐个处理字段 for (UnMappedColumnAutoMapping mapping : autoMappings) { // 2.1 通过 TypeHandler 从结果集获取字段值(并转换为 Java 类型) final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column); if (value != null) { foundValues = true; } // 2.2 通过 MetaObject 给对象属性赋值(支持 null 值处理配置) if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) { // 给属性赋值(如 user.setName(value)) metaObject.setValue(mapping.property, value); } } return foundValues; }
关键步骤解析
- 创建自动映射列表(createAutomaticMappings):获取未映射的字段,与JavaBean的属性建立映射关系,并根据JDBC的类型,绑定TypeHandler,带有缓存,防止每次都重复映射。
- 通过TypeHandler转换类型 :熟悉的TypeHandler,与上一节相反,这个是从JDBC转为Java类型。
- 通过MetaObject赋值:利用 MyBatis 的反射工具 MetaObject 给目标对象的属性赋值,支持配置 callSettersOnNulls(即使值为null也调用 setter 方法),以及对基本类型(primitive)的特殊处理(避免给基本类型赋null导致异常)。
applyPropertyMappings
方法,为Java对象赋值的逻辑与这个相同,但还有些处理嵌套和延迟加载的逻辑,这里不深入研究了。
三、小结
ResultSetHandler
是负责将JDBC 结果集(ResultSet)转换为Java对象的核心组件。本文对他的实现做了浅显的分析,复杂的嵌套结果映射,以及嵌套结果的延迟加载,本文没有涉及(有点复杂)。
到此为止,MyBatis四大核心组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)已经分析完成,后续准备分析下Mybatis的插件机制,这个可能是我们了解Mybatis源码最重要的用途。
到此这篇关于MyBatis中ResultSetHandler的具体使用的文章就介绍到这了,更多相关MyBatis ResultSetHandler内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!