Skip to content

结果集映射

This content is not available in your language yet.

结果映射既要把一行列值填进 Java Bean,又要在多行结果中合并同一个父对象、创建子集合,并避免嵌套查询无限递归。

先给答案:结果映射是在“数据库列名”和“对象属性图”之间补一层可解释的契约

Section titled “先给答案:结果映射是在“数据库列名”和“对象属性图”之间补一层可解释的契约”

数据库返回的是行和列,Java 代码需要的是对象、嵌套对象或集合。MyBatis 不把这两种结构硬编码成一一对应,而是先构建 ResultMap,再根据列值、属性类型和嵌套映射规则逐步装配对象。

映射越灵活,运行时决策越多:自动映射可能带来便利,也可能把列名拼写错误变成静默的 null;嵌套查询可以延迟加载,却会把一次查询放大成 N+1。阅读结果映射时不能只看“字段有没有填上”,还要追踪对象何时创建、嵌套查询何时触发、列为空时选择了哪条分支。

ResultSet -> ResultSetWrapper -> DefaultResultSetHandler
|- ResultMap / Discriminator
|- createResultObject
|- applyPropertyMappings
`- nestedResultObjects + CacheKey
  • DefaultResultSetHandler#handleResultSets 遍历多个 result set,并按 result maps 处理(mybatis-3/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java:211-250)。
  • handleRowValues 根据是否有 nested result map 选择简单或复杂路径(mybatis-3/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java:329-357)。
  • getRowValue 创建结果对象,再处理属性映射和 lazy loader(mybatis-3/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java:461-482)。
  • createResultObject 根据 constructor mappings、type handler 和默认构造器选择实例化路径(mybatis-3/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java:719-820)。
  • applyPropertyMappings 先处理显式 ResultMapping,再交给自动映射补齐(mybatis-3/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java:545-675)。
  • createAutomaticMappings 根据列名和前缀推导 property,并缓存自动映射(mybatis-3/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java:644-719)。
  • nestedResultObjects 按 CacheKey 暂存对象,避免一对多 join 每行创建父对象(mybatis-3/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java:81-101)。
  • createRowKey 将 result map id 与标识列值组合成 row key(mybatis-3/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java:1490-1525)。
  • applyNestedResultMappings 生成 combined key,复用或创建子对象(mybatis-3/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java:1150-1338)。
  • nested select 可创建 ResultLoader,在属性访问时再加载(mybatis-3/src/main/java/org/apache/ibatis/executor/loader/ResultLoader.java:59-91)。

替代方案:按 ResultSet 行直接创建完整对象树。 为什么不行:一对多 join 会重复返回父列,直接创建会产生重复父对象和重复子集合。 证据:applyNestedResultMappings 使用 combinedKey 查询 nestedResultObjects,并把新对象放回同一 Map(DefaultResultSetHandler.java:1297-1338)。

场景 现象 原因 规避
join 没有稳定 id 父对象重复 row key 无法识别同一父对象 为 nested result map 配置 <id>
下划线与驼峰不一致 属性为 null 自动映射规则未转换 开启驼峰或显式 result
lazy loading 关闭 关联 SQL 立即执行 nested select 按配置加载 统一 lazyLoading/fetchType
一对多结果过大 内存和 GC 飙升 结果树在内存合并 分页、游标或拆分查询

“父 key + 子 key + pending object”是把扁平事件流还原为树结构的通用算法,适合 CSV join、消息聚合和批量导入。

面试锚点

  • MyBatis 如何处理一对多 join 的父对象重复?
  • 自动映射和显式 ResultMap 的优先级是什么?
  • 嵌套查询为什么可能造成 N+1?