【源码解读之 Mybatis】【核心篇】-- 第8篇:ResultSetHandler结果集处理

news/2025/11/30 15:29:34/文章来源:https://www.cnblogs.com/lunzi1992/p/19289422

第8篇:ResultSetHandler结果集处理

1. 学习目标确认

1.0 第7篇思考题解答

思考题1:DefaultParameterHandler的参数值获取为什么要设计优先级策略?

答案要点

  • 额外参数优先:foreach动态SQL生成的参数优先级最高
  • 空参数处理:避免空指针异常
  • 基本类型判断:单参数场景性能优化
  • 复杂对象反射:POJO对象通过MetaObject获取值
  • 设计优势:灵活支持多种参数类型,统一处理逻辑

思考题2:ParameterHandler如何与TypeHandler协作完成类型转换?

答案要点

  • 协作流程:获取参数值 → 选择TypeHandler → 调用setParameter()
  • 类型匹配:根据javaType和jdbcType选择TypeHandler
  • 职责分离:ParameterHandler管理参数,TypeHandler转换类型
  • TypeHandler复用:在参数设置和结果映射中都使用

思考题3:在什么情况下会产生额外参数(AdditionalParameter)?它们是如何生成和使用的?

  • foreach 动态 SQL:<foreach> 在展开时会为每次迭代生成临时参数(如 __frch_id_0__frch_id_1),通过 BoundSql.setAdditionalParameter() 写入。
  • <bind> 节点:基于 OGNL 计算表达式生成新参数,注入到执行上下文,同样体现在 BoundSql 的额外参数集。
  • 嵌套查询传参:association/collection 使用 select 属性时,会把父行的列值作为参数传递给子查询,相关值会以额外参数方式参与参数解析。
  • 使用方式:ParameterHandler 在取值时优先检查 boundSql.hasAdditionalParameter(name),命中则直接使用这些临时参数,保证动态生成的数据被正确绑定。

示例:

<select id="findByIds" resultType="User">SELECT * FROM user WHERE id IN<foreach collection="list" item="id" open="(" separator="," close=")">#{id}</foreach>
</select>

执行时会生成 __frch_id_0__frch_id_1... 并在 DefaultParameterHandler 内部以“额外参数优先”策略取值。

思考题4:如何设计一个通用的参数处理器来支持多种扩展功能(如加密、验证、日志等)?

  • 装饰器/管道化:在 DefaultParameterHandler 外层构建装饰器,分阶段执行“验证 → 转换/加密 → 记录日志 → 委托设置参数”的流水线。
  • 职责拆分:每个扩展功能独立实现(如 EncryptionParameterHandlerValidationParameterHandler),通过组合或顺序调用复用。
  • TypeHandler 协作:扩展仅在“取值”阶段介入,不破坏 TypeHandler 的类型转换职责,确保兼容性。
  • 统一启用方式:可通过插件拦截或自定义 LanguageDriver 控制何时创建自定义 ParameterHandler
  • 失败处理:验证不通过抛出明确异常;加密失败回滚为原值或抛错,避免脏数据入库。

最小实现建议:在 setParameters(ps) 前先执行校验与转换,再委托给 DefaultParameterHandler 完成最终绑定。

1.1 本篇学习目标

  1. 深入理解ResultSetHandler的设计思想和核心职责
  2. 掌握DefaultResultSetHandler的结果映射流程
  3. 理解ResultMap配置和自动映射机制
  4. 掌握嵌套查询和嵌套结果映射
  5. 了解多结果集、游标查询等高级特性

2. ResultSetHandler接口定义

/*** 结果集处理器接口*/
public interface ResultSetHandler {/*** 处理查询结果集*/<E> List<E> handleResultSets(Statement stmt) throws SQLException;/*** 处理游标结果集*/<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;/*** 处理存储过程输出参数*/void handleOutputParameters(CallableStatement cs) throws SQLException;
}

2.1 结果集处理流程图

sequenceDiagramparticipant RSH as "DefaultResultSetHandler"participant Stmt as "Statement"participant RSW as "ResultSetWrapper"participant RM as "ResultMap"participant MO as "MetaObject"participant TH as "TypeHandler"participant Caller as "List<E>"RSH->>Stmt: getResultSet()RSW->>RSH: wrap(ResultSet)loop 每个 ResultMapRSH->>RM: getResultMap(index)loop 每行数据RSH->>MO: createResultObject()alt 自动映射RSH->>RSW: getColumnValue()RSH->>TH: getResult(column)MO->>MO: setValue(property, value)else 手动映射RSH->>TH: getResult(column)MO->>MO: setValue(property, value)endendRSH->>Stmt: getMoreResults()endRSH-->>Caller: 列表结果Note over RSH: ResultMap 缓存 + TypeHandler 复用

3. DefaultResultSetHandler核心实现

3.1 处理结果集主流程

@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {final List<Object> multipleResults = new ArrayList<>();int resultSetCount = 0;// 获取第一个ResultSetResultSetWrapper rsw = getFirstResultSet(stmt);List<ResultMap> resultMaps = mappedStatement.getResultMaps();// 处理每个ResultSetwhile (rsw != null && resultSetCount < resultMaps.size()) {ResultMap resultMap = resultMaps.get(resultSetCount);handleResultSet(rsw, resultMap, multipleResults, null);rsw = getNextResultSet(stmt);resultSetCount++;}return collapseSingleResultList(multipleResults);
}

3.2 处理每一行数据

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {// 1. 创建结果对象Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {MetaObject metaObject = configuration.newMetaObject(rowValue);boolean foundValues = false;// 2. 应用自动映射if (shouldApplyAutomaticMappings(resultMap, false)) {foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);}// 3. 应用属性映射foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;rowValue = foundValues ? rowValue : null;}return rowValue;
}

3.3 与TypeHandler协作(结果取值)

  • 简单类型(如String、Integer、Long、Date等)会直接使用对应的TypeHandler从结果集中取值,无需创建目标对象。
  • 复杂类型(POJO)先创建结果对象,再通过自动映射和属性映射填充字段;嵌套关联遵循ResultMap定义。

示例:使用TypeHandler直接从ResultSet读取列值

TypeHandler<String> stringHandler = configuration.getTypeHandlerRegistry().getTypeHandler(String.class);
String userName = stringHandler.getResult(rsw.getResultSet(), "user_name");TypeHandler<Long> longHandler = configuration.getTypeHandlerRegistry().getTypeHandler(Long.class);
Long userId = longHandler.getResult(rsw.getResultSet(), "user_id");

关键点:

  • hasTypeHandlerForResultObject(rsw, resultMap.getType())为true时,走“简单类型直取”路径;否则进入对象映射流程。
  • 结果侧与参数侧复用同一套TypeHandler体系,保证类型转换一致性。

4. ResultMap结果映射配置

4.1 基本ResultMap配置

<resultMap id="userResultMap" type="User"><!-- ID映射 --><id property="id" column="user_id"/><!-- 普通属性映射 --><result property="name" column="user_name"/><result property="email" column="user_email"/><!-- 一对一关联 --><association property="address" javaType="Address"><id property="id" column="addr_id"/><result property="street" column="street"/></association><!-- 一对多集合 --><collection property="orders" ofType="Order"><id property="id" column="order_id"/><result property="orderNo" column="order_no"/></collection>
</resultMap>

4.2 自动映射机制

<settings><!-- 自动映射级别:NONE, PARTIAL, FULL --><setting name="autoMappingBehavior" value="PARTIAL"/><!-- 驼峰命名转换 --><setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

说明:

  • NONE:仅按ResultMap显式定义映射,不做自动填充。
  • PARTIAL:对未显式映射的列做“保守自动映射”,避免覆盖已有映射;默认推荐。
  • FULL:尽可能尝试自动映射,适合字段命名规范统一的场景,但需注意覆盖风险。
  • 配合 mapUnderscoreToCamelCase=true可自动将user_name映射到userName。

5. 嵌套映射处理

5.1 嵌套查询(N+1问题)

<resultMap id="userMap" type="User"><id property="id" column="id"/><result property="name" column="name"/><!-- 嵌套查询:会产生N+1问题 --><collection property="orders" column="id" select="selectOrdersByUserId"/>
</resultMap>

5.2 嵌套结果映射(解决N+1)

<resultMap id="userWithOrdersMap" type="User"><id property="id" column="user_id"/><result property="name" column="user_name"/><!-- 嵌套结果映射:一次JOIN查询 --><collection property="orders" ofType="Order"><id property="id" column="order_id"/><result property="orderNo" column="order_no"/></collection>
</resultMap><select id="selectUserWithOrders" resultMap="userWithOrdersMap">SELECT u.id as user_id,u.name as user_name,o.id as order_id,o.order_no as order_noFROM t_user uLEFT JOIN t_order o ON u.id = o.user_idWHERE u.id = #{id}
</select>

5.3 延迟加载

<settings><!-- 开启延迟加载 --><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/>
</settings><resultMap id="userMap" type="User"><id property="id" column="id"/><result property="name" column="name"/><!-- 延迟加载订单 --><collection property="orders" column="id" select="selectOrdersByUserId"fetchType="lazy"/>
</resultMap>

实现原理:通过代理对象延迟触发查询。常见实现为CGLIB/Javassist创建字节码代理或JDK动态代理包裹目标对象;aggressiveLazyLoading=false时仅在访问被标记为 lazy的属性时触发SQL,设置为 true则更“激进”,可能在更多方法调用中触发加载。

6. 高级特性

6.1 游标查询(Cursor)

/*** 游标查询适合处理大量数据*/
try (SqlSession session = factory.openSession()) {UserMapper mapper = session.getMapper(UserMapper.class);// 返回游标,逐条读取try (Cursor<User> cursor = mapper.selectAllUsers()) {for (User user : cursor) {processUser(user);}}
}

6.2 自定义ResultHandler

/*** 自定义结果处理器实现流式处理*/
public class CustomResultHandler implements ResultHandler<User> {private int count = 0;@Overridepublic void handleResult(ResultContext<? extends User> context) {User user = context.getResultObject();processUser(user);count++;// 可以控制何时停止if (count >= 1000) {context.stop();}}
}// 使用
session.select("selectAllUsers", null, new CustomResultHandler());

6.3 多结果集处理

<select id="getUserAndOrders" statementType="CALLABLE" resultSets="users,orders">{call get_user_and_orders(#{userId})}
</select>

7. 性能优化

7.1 避免N+1问题

// ❌ 错误:嵌套查询产生N+1问题
<collection property="orders" select="selectOrders"/>// ✅ 正确:嵌套结果映射,一次JOIN查询
<collection property="orders" ofType="Order"><id property="id" column="order_id"/>
</collection>

7.2 大数据量处理

// 1. 使用游标查询
Cursor<User> cursor = mapper.selectLargeData();// 2. 使用ResultHandler
session.select("selectLargeData", handler);// 3. 分页查询
RowBounds bounds = new RowBounds(offset, limit);
List<User> users = mapper.selectByPage(bounds);

7.3 ResultMap缓存

// ResultMap配置会被缓存,重复使用无需重新解析
private List<UnMappedColumnAutoMapping> createAutomaticMappings(...) {final String mapKey = resultMap.getId() + ":" + columnPrefix;// 从缓存获取List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);if (autoMapping == null) {// 创建并缓存autoMapping = new ArrayList<>();autoMappingsCache.put(mapKey, autoMapping);}return autoMapping;
}

8. 实践案例

8.1 完整映射示例

// 实体类
public class User {private Long id;private String name;private Address address;      // 一对一private List<Order> orders;   // 一对多
}
<resultMap id="userDetailMap" type="User"><id property="id" column="user_id"/><result property="name" column="user_name"/><association property="address" javaType="Address"><id property="id" column="addr_id"/><result property="street" column="street"/></association><collection property="orders" ofType="Order"><id property="id" column="order_id"/><result property="orderNo" column="order_no"/></collection>
</resultMap>

8.2 性能测试对比

public class ResultSetHandlerPerformanceTest {private static SqlSessionFactory factory;static {try (InputStream is = Resources.getResourceAsStream("mybatis-config.xml")) {factory = new SqlSessionFactoryBuilder().build(is);} catch (Exception e) {throw new RuntimeException(e);}}public static void main(String[] args) {try (SqlSession session = factory.openSession()) {long time1 = testNestedQuery(session);long time2 = testNestedResultMap(session);System.out.printf("嵌套查询耗时: %dms%n", time1);System.out.printf("嵌套结果耗时: %dms%n", time2);System.out.printf("性能提升: %.1f%%%n", (time1 - time2) * 100.0 / time1);// 实测通常 85%~95% 提升}}private static long testNestedQuery(SqlSession session) {long start = System.currentTimeMillis();User user = session.selectOne("getUserWithNestedQuery", 1L);user.getOrders().size(); // 触发 N+1return System.currentTimeMillis() - start;}private static long testNestedResultMap(SqlSession session) {long start = System.currentTimeMillis();User user = session.selectOne("getUserWithNestedResultMap", 1L);user.getOrders().size(); // 一次 JOINreturn System.currentTimeMillis() - start;}
}

9. 常见问题

9.1 结果映射失败

问题:属性值为null

排查

  1. 检查列名是否匹配
  2. 检查ResultMap配置
  3. 检查TypeHandler
  4. 开启SQL日志

解决

<!-- 开启驼峰转换 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>

9.2 N+1查询问题

解决方案

  1. 使用嵌套结果映射代替嵌套查询
  2. 开启延迟加载
  3. 使用批量查询优化

9.3 大数据量OOM

解决方案

// 使用游标或ResultHandler
try (Cursor<User> cursor = mapper.selectAll()) {for (User user : cursor) {process(user);}
}

9.4 源码调试指导

建议断点:

  • DefaultResultSetHandler.handleResultSets()
  • DefaultResultSetHandler.handleResultSet(...)
  • DefaultResultSetHandler.getRowValue(...)
  • DefaultResultSetHandler.applyAutomaticMappings(...)
  • DefaultResultSetHandler.applyPropertyMappings(...)
  • DefaultResultSetHandler.hasTypeHandlerForResultObject(...)

调试小贴士:

  • 开启日志:<setting name="logImpl" value="STDOUT_LOGGING"/>
  • 打印列到属性的映射关系,快速定位空值来源:
for (String column : rsw.getColumnNames()) {System.out.println("column=" + column + ", value=" + rsw.getResultSet().getObject(column));
}

10. 小结

核心职责

  • 将JDBC ResultSet映射为Java对象
  • 处理简单和复杂嵌套映射
  • 支持自动映射和手动配置
  • 实现延迟加载和游标查询

设计亮点

  • 灵活的ResultMap配置机制
  • 智能的自动映射策略
  • 高效的嵌套结果处理
  • 完善的类型转换体系

性能优化

  • 避免N+1查询问题
  • 合理使用嵌套结果映射
  • 大数据量使用游标或ResultHandler
  • 善用ResultMap缓存

思考题

  1. ResultSetHandler与ParameterHandler有什么本质区别?它们如何协作完成完整的数据流转?
  2. 嵌套查询和嵌套结果映射各有什么优缺点?在什么场景下应该选择哪种方式?
  3. 延迟加载的实现原理是什么?为什么需要使用代理对象?
  4. 如何设计一个通用的ResultSetHandler来支持多种扩展功能(如脱敏、审计、缓存等)?
  5. 在高并发场景下,ResultSetHandler的哪些设计可能成为性能瓶颈?如何优化?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/981955.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

samba的常见问题

环境: Server ip : 10.177.245.232 client ip : 10.177.245.230挂载之后的权限问题: Server: Smb.confClient: Smbclient --list=10.177.245.232 mount -t cifs //10.177.245.232/user1 /test/ -o username=user…

2025年佛山地区桶装水配送服务商推荐:比较好的桶装水送水电

本榜单聚焦佛山本地桶装水配送服务需求,结合用户真实反馈、服务覆盖能力与口碑评价,筛选出五家靠谱服务商,为家庭及企业用户提供精准选型参考,解决饮水配送的时效、安全与成本痛点。 TOP1 推荐:佛山市水专家电子商…

2025年中国十大知名的活动策划企业推荐:诚信的活动策划企业

本榜单依托全维度市场调研与真实行业口碑,深度筛选出十家标杆企业,为企业选型提供客观依据,助力精准匹配适配的服务伙伴。 TOP1 推荐:万贝(上海)文化传播有限公司 推荐指数:★★★★★ 口碑评分:国内首推的知名活…

tts服务

tts服务GitHub - zuoban/tts: tts 服务 漫思

2025年高温测试机构推荐与高温实验机构排名,高温试验品牌企

在工业制造、航空航天、汽车电子等领域,高温测试是验证产品可靠性与环境适应性的核心环节。精准的高温试验数据不仅关乎产品质量安全,更直接影响企业的市场竞争力与行业准入资格。面对市场上良莠不齐的检测机构,如何…

2025年十大接地箱定制生产厂家排行榜,专业的接地箱厂家推荐

为帮助电力企业高效锁定适配自身需求的接地箱合作伙伴,避免选型走弯路,我们从技术研发实力(如专利数量、产学研合作深度)、产品工艺水准(含原材料质量、防护等级)、全周期服务质量(覆盖定制方案到售后维护)及真…

KFCoder - 敏捷冲刺日志-1st

第 1 篇 Scrum 冲刺博客(2025 年 11 月 27 日) 一、团队成员 Alpha 阶段认领任务成员 认领任务(关联 Work Item ID) 任务类型 预估工时(小时)徐新曜 SB01(后端基础架构搭建)、SB05(用户登录接口)、SB15(打卡…

自动驾驶中的传感器技术75——Navigation(12) - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Spring AI实现一个简单的对话机器人

在其他地方查看本文:Spring AI实现一个简单的对话机器人 - Liu Zijians Blog - 一个个人博客网站本文通过Spring AI基于DeepSeek大模型,以Prompt模式,开发一个智能聊天机器人,并进行对话。Spring AI必须基于jdk-21…

惠算科技 GEO 优化优选惠州本地生活推荐:生成式引擎优化时代的本地商家获客破局指南

在惠州本地生活领域,生成式引擎优化(GEO)正展现出巨大的市场潜力。据相关数据显示,惠州本地生活 GEO 市场规模近年来以每年 30% 的增长率快速扩张。像惠算科技 GEO、一些知名的 AI 搜索优化工具以及具备意图识别功…

Genie 2:大规模基础世界模型的技术突破

本文介绍了Genie 2基础世界模型的技术架构与能力,这是一个能够从单张提示图像生成可交互3D环境的扩散模型。模型支持键盘鼠标控制,具备长时记忆、物理模拟、角色动画等能力,为具身智能体训练提供无限多样的虚拟环境…

薄壁镀锌方管生产厂TOP5权威推荐:服务不错的镀锌方管工厂甄

建筑工程中,薄壁镀锌方管因防腐性强、自重轻、安装便捷等优势,成为轻钢结构、幕墙、物流货架等场景的核心选材。2024年数据显示,国内薄壁镀锌方管市场需求同比增长32%,但41%的工程投诉集中在材质不符规格缺失售后滞…

2025年靠谱的雕塑定制品牌企业推荐,现代雕塑定制设计哪家好

在城市更新与文化建设的浪潮中,一座独具匠心的雕塑不仅是空间的点睛之笔,更是城市精神与品牌文化的具象表达。面对市场上良莠不齐的雕塑定制服务,如何找到兼具艺术创意、工艺实力与交付保障的合作伙伴?以下结合行业…

Xcode26新特性与iOS26适配指南

Xcode 26 作为苹果适配 iOS 26、macOS 15 等新一代系统的开发工具,在开发效率、AI 赋能、跨平台协同及性能优化上有显著升级;iOS 26 则带来了全新系统能力与合规要求,开发者需重点关注适配要点。以下是核心内容整理…

鸿蒙开发中,module.json5配置文件详解

module.json5 是鸿蒙系统中用于定义应用模块的核心配置文件。它包含模块的基本信息、入口能力、支持的设备类型、权限请求等内容,是应用开发和部署的基础。配置文件结构以下是一个典型的 module.json5 配置示例:{&qu…

茶叶商标购买平台指南:2025 哪个平台最好?从标源到过户一文说透

2025 年茶叶商标购买平台首选福象商标宝 AI(微信小程序) !综合标源真实性、交易安全性、过户效率、费用透明度及茶叶行业适配性五大维度测评,其以 9.9/10 的综合评分稳居榜首。依托福象知识产权集团的专业背景,凭…

2025年智能机械设备研发加工厂哪家更值得选?智能机械设备源

在智能制造升级浪潮下,企业对智能机械设备的自主化、高精度、高效率需求日益迫切。本文依托技术实力、客户口碑、行业适配性三大核心维度,筛选出五家标杆企业,为工业企业选型提供客观参考,助力破解卡脖子困境与生产…

2025年十大专业检测仪器公司排行榜,北京时代光南检测技术有

为帮企业高效锁定适配自身需求的检测仪器合作伙伴,避免选型走弯路,我们从产品技术精准度(如检测数据误差率、设备稳定性)、行业场景适配能力(含多领域定制化方案、极端环境耐受性)、全周期服务质量(覆盖设备选型…

2025年十大讲解机器人供应企业排名,讲解机器人哪家好

为帮助企业高效筛选适配自身场景的智能讲解机器人服务商,避开功能虚标、售后脱节、案例不符的选型陷阱,我们从售后服务质量(响应速度、质保政策、退换货保障)、成功案例适配性(垂直行业落地效果、场景覆盖广度)、…

2025年全国无损检测仪器服务商年度排名:北京时代光南检测技

TOP1推荐:北京时代光南检测技术有限公司 评价指数:★★★★★ 口碑评分:99分 行业表现:A++++级 介绍:北京时代光南检测技术有限公司是国内专业无损检测仪器领域的企业,深耕检测设备研发、销售与服务多年,始终以…