3.1 从resultType到ResultMap


文档摘要

3.1 从 resultType 到 ResultMap 本节摘要:resultType 依赖"列名等于属性名"的自动映射,列别名与驼峰开关是它的两大助推;对不上的列要靠 ResultMap 手工对表。本节讲清自动映射的边界、ResultMap 的结构与继承复用,用"查得出来但字段是 null"的病例串联排查路径。 进入结果出端。查询执行完,数据库交回一张二维表;把它装进 Java 对象,靠的是本节的两套机制。 自动映射的边界在哪里 先看最简单的形态: resultType 声明"结果装进 User"。装配规则一句话:列名与属性名相同(忽略大小写)的自动搬运,对不上的列被丢弃,对不上的属性保持默认值。 边界马上就来了。

3.1 从 resultType 到 ResultMap

本节摘要:resultType 依赖"列名等于属性名"的自动映射,列别名与驼峰开关是它的两大助推;对不上的列要靠 ResultMap 手工对表。本节讲清自动映射的边界、ResultMap 的结构与继承复用,用"查得出来但字段是 null"的病例串联排查路径。

进入结果出端。查询执行完,数据库交回一张二维表;把它装进 Java 对象,靠的是本节的两套机制。

自动映射的边界在哪里

先看最简单的形态:

<select id="findById" resultType="User"> SELECT id, username, email FROM users WHERE id = #{id} </select>

resultType 声明"结果装进 User"。装配规则一句话:列名与属性名相同(忽略大小写)的自动搬运,对不上的列被丢弃,对不上的属性保持默认值

边界马上就来了。数据库命名习惯下划线,Java 命名习惯驼峰,users 表有列 created_time,User 类有属性 createdTime——名字对不上,查出来永远是 null。三种解法按成本排序:

解法一:列别名,零配置,改 SQL:

<select id="findById" resultType="User"> SELECT id, username, email, created_time AS createdTime, <!-- 别名对齐属性名 --> last_login_time AS lastLoginTime FROM users WHERE id = #{id} </select>

解法二:驼峰开关,一处配置全局生效(1.2 节已开):

<settings> <!-- created_time 自动映射到 createdTime --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>

解法三:ResultMap 手工对表——别名要改每条 SQL,驼峰开关管不了"名字毫无规律"的列(比如历史表里的 cname 要映射到 companyName),这时只剩手工:

<!-- 手工对表:列与属性的每一条对应关系写明 --> <resultMap id="userMap" type="User"> <id property="id" column="id"/> <result property="companyName" column="cname"/> <result property="email" column="email"/> <result property="createdTime" column="created_time"/> </resultMap> <select id="findById" resultMap="userMap"> SELECT id, cname, email, created_time FROM users_legacy WHERE id = #{id} </select>

03-01-fig01-3

⚠️ 常见坑:字段 null 不是异常是静默行为。SQL 命中了一行、日志 Total 为 1,对象里某些字段却是 null——九成是列名没对上。排查顺序固定:先看日志里 Preparing 的实际列名,再对照属性名,最后决定用别名、开关还是 ResultMap。

ResultMap 的解剖

把 resultMap 的元素逐个看一遍:

<resultMap id="userMap" type="User" autoMapping="PARTIAL"> <!-- id 元素:主键列。除普通映射外,它还是结果去重的标记(3.3 节) --> <id property="id" column="id"/> <!-- result 元素:普通列到属性 --> <result property="email" column="email"/> <!-- 单列指定 TypeHandler:金额列以分存储 映射时除以一百 --> <result property="balance" column="balance" typeHandler="com.example.handler.MoneyTypeHandler"/> <!-- constructor:构造器注入,绕过无参构造加 setter 的默认路径 --> <constructor> <arg column="username" javaType="String"/> </constructor> </resultMap>

两个进阶点。autoMapping 档位:PARTIAL 是默认——ResultMap 里声明过的列走声明,没声明的列若恰好同名仍自动映射;FULL 则全部自动;NONE 关闭自动。用 PARTIAL 时"声明一半、自动一半"是允许的,这让 ResultMap 只接管对不上的列,其余交给自动机制。

继承复用:两个表结构相似时,公共部分抽成父映射:

<!-- 父映射:公共列 --> <resultMap id="baseUserMap" type="User"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="email" column="email"/> </resultMap> <!-- 子映射:extends 继承父映射 只补差异列 --> <resultMap id="adminUserMap" type="AdminUser" extends="baseUserMap"> <result property="roleLevel" column="role_level"/> <result property="deptName" column="dept_name"/> </resultMap>

继承在"同一张表映射多个视图对象"的场景最划算——基础字段一份定义,各视图补自己的扩展字段。

完整病例:一次报表导出的 null 字段

背景:报表导出功能上线两周后,运营反馈"会员等级和部门两列一直是空的"。日志显示查询正常,对象非 null,就是这两个字段空。

操作:打开日志看 Preparing 行的实际列:

==> Preparing: SELECT id, username, email, vip_level, dept_name, created_time FROM users ...

再对照实体:属性名是 vipLevel、deptName、createdTime。created_time 有驼峰开关兜底没问题;vip_level、deptName 两处,一处靠开关,另一处(dept_name 对 deptName)也该靠开关——逐项检查发现:实体里写的是 deptNm(历史拼写错误),列名是 dept_name,开关救不了拼写差异。

结果:为这两列补一个 ResultMap,显式对表:

<resultMap id="reportUserMap" type="User" autoMapping="PARTIAL"> <id property="id" column="id"/> <result property="deptNm" column="dept_name"/> <result property="vipLevel" column="vip_level"/> </resultMap>

解读:自动映射的静默失败(查得出来、字段为 null)比报错更危险——它不阻断流程,只污染数据。日志里的实际列名永远是排查的第一证据。

变式:如果这个实体同时在多个查询里使用,把 ResultMap 升级为全局唯一定义、所有查询引用它,让"列到属性"的对应关系也收进 2.4 节讲的"片段库"思路——映射关系单点维护。

💡 关键直觉:resultType 与 resultMap 不是新旧两种写法,而是同一套装配机制的"自动档"与"手动档"。边界内用自动档省心,边界外手动档精确,混用(PARTIAL)是常态。

本节要点回顾

  • 自动映射规则:列名与属性名相同则搬运,对不上的列静默丢弃——null 字段的头号来源
  • 三条通路:同名直达、别名强制、驼峰开关桥接、ResultMap 手工对表,按成本递增选用
  • autoMapping 三档:PARTIAL 混合装配是默认,声明列走声明、同名列走自动
  • id 元素双职责:普通映射之外还是结果去重标记,第 3.3 节的主角
  • 继承复用:公共列抽父映射,视图对象只补差异
  • 排查路径:null 字段先看日志实际列名,再对照属性名,三选一修复

单表结果的进出都通了,下一章第一站先处理"一个对象里嵌另一个对象":一对一关联的两种缝合术。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U