3.2 一对一关联:association的两种缝法


文档摘要

3.2 一对一关联:association 的两种缝法 本节摘要:一对一关联用 association 元素缝合,有嵌套结果(join 一次查询、结果里装配)与嵌套查询(分步查询、可懒加载)两种缝法。本节以"用户带证件"为病例完整演示两种写法,对比各自的 SQL 形态与查询次数,讲清 column 传参与懒加载开关,并给出选择依据。 上一节把单表的列对上了属性。这一节让对象里长出另一个对象——用户实体里那个类型为 IdCard 的字段。 病例背景:查用户要连证件一起出 用 resultType 无法把两表的结果装进嵌套结构——自动映射只认平铺的同名列。这时 association 登场。

3.2 一对一关联:association 的两种缝法

本节摘要:一对一关联用 association 元素缝合,有嵌套结果(join 一次查询、结果里装配)与嵌套查询(分步查询、可懒加载)两种缝法。本节以"用户带证件"为病例完整演示两种写法,对比各自的 SQL 形态与查询次数,讲清 column 传参与懒加载开关,并给出选择依据。

上一节把单表的列对上了属性。这一节让对象里长出另一个对象——用户实体里那个类型为 IdCard 的字段。

病例背景:查用户要连证件一起出

-- 两张表:一个用户对应一张证件(一对一) CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, id_card_id INT ); CREATE TABLE id_cards ( id INT PRIMARY KEY AUTO_INCREMENT, card_no VARCHAR(30) NOT NULL, -- 证件号 issuing_authority VARCHAR(100) -- 签发机关 );
// 实体:User 内嵌一个 IdCard public class User { private Integer id; private String username; private IdCard idCard; // 关联对象 // 省略 getter 与 setter } public class IdCard { private Integer id; private String cardNo; private String issuingAuthority; // 省略 getter 与 setter }

用 resultType 无法把两表的结果装进嵌套结构——自动映射只认平铺的同名列。这时 association 登场。

缝法一:嵌套结果,join 一次查完

一条 SQL 把两表 join 成宽行,ResultMap 声明"哪些列装配进内嵌对象":

<resultMap id="userWithCardMap" type="User"> <id property="id" column="u_id"/> <result property="username" column="username"/> <!-- association:一对一内嵌对象,javaType 指明目标类型 --> <association property="idCard" javaType="IdCard"> <id property="id" column="c_id"/> <result property="cardNo" column="card_no"/> <result property="issuingAuthority" column="issuing_authority"/> </association> </resultMap> <select id="findUserWithCard" parameterType="int" resultMap="userWithCardMap"> SELECT u.id AS u_id, u.username, c.id AS c_id, c.card_no, c.issuing_authority FROM users u LEFT JOIN id_cards c ON u.id_card_id = c.id WHERE u.id = #{id} </select>
User u = mapper.findUserWithCard(1); // 输出:User{id=1, username='zhangsan', // idCard=IdCard{id=5, cardNo='110...X', issuingAuthority='公安局'}} System.out.println(u);

两个细节值得盯住。列别名防撞车:两表都有 id 列,别名 u_id 与 c_id 把它们分开,ResultMap 按别名各取各的。内层也有 id 元素:一对一虽然不涉及多行合并,但写上主键标记是好习惯——它让框架在缓存场景能正确标识这个内嵌对象。

缝法二:嵌套查询,分步再装配

主查询只查用户表,association 声明"拿主查询的某列去执行另一条语句":

<resultMap id="userWithCardStepMap" type="User"> <id property="id" column="id"/> <result property="username" column="username"/> <!-- 嵌套查询:column 指定主查询哪一列传给子查询作参数 --> <association property="idCard" column="id_card_id" select="findCardById" fetchType="lazy"/> </resultMap> <select id="findUserWithCardStep" parameterType="int" resultMap="userWithCardStepMap"> SELECT id, username, id_card_id FROM users WHERE id = #{id} </select> <!-- 子查询:独立语句,可被其他映射复用 --> <select id="findCardById" parameterType="int" resultType="IdCard"> SELECT id, card_no AS cardNo, issuing_authority AS issuingAuthority FROM id_cards WHERE id = #{id} </select>

执行日志会打出两条独立的 SQL:

==> Preparing: SELECT id, username, id_card_id FROM users WHERE id = ? ==> Parameters: 1(Integer) ==> Preparing: SELECT id, card_no, issuing_authority FROM id_cards WHERE id = ? ==> Parameters: 3(Integer)

第二条语句的参数 3 来自第一条结果的 id_card_id 列——这就是 column 传参:把主查询结果的一列,作为子查询的入参

两种缝法的对照与选择

两种缝法的对照与选择

选择依据压成两句:查列表用嵌套结果(一次往返,join 代价可控);查单条且关联对象不常用时用嵌套查询加懒加载(默认不触发子查询,用到才查)。

懒加载:改变的是查询时机

fetchType="lazy" 让子查询推迟到真正访问关联属性的那一刻:

User u = mapper.findUserWithCardStep(1); // 此刻日志只有主查询一条 SQL System.out.println(u.getUsername()); // 不触发子查询 System.out.println(u.getIdCard().getCardNo()); // 访问 idCard 属性时,日志才出现子查询那条 SQL

懒加载的实现是给返回对象套一层代理,首次访问关联属性时代理补发子查询。全局开关 lazyLoadingEnabled 可把所有嵌套查询默认置为懒(aggressiveLazyLoading 关闭后,访问非关联属性不触发加载)。一条使用纪律:懒加载的会话必须仍处于打开状态——会话已关闭再访问懒属性,会抛异常。这也是 1.4 节"会话与工作单元同寿"红线的另一个理由。

⚠️ 常见坑:嵌套查询用在列表上。查 20 个用户、每个用户触发一次证件子查询,就是 21 条 SQL——这就是著名的 N 加一问题。6.3 节性能优化会给出完整的检测与消灭手段;本节的结论很简单:列表查询一律嵌套结果。

💡 关键直觉:association 的 column 属性在两种缝法里语义不同——嵌套结果里它是"列的记号"(配合别名),嵌套查询里它是"传给子查询的参数"。看同一个属性名,先分清脚下是哪种缝法。

本节要点回顾

  • 两种缝法:嵌套结果一条 join 装配宽行;嵌套查询主子两条、column 传参衔接
  • 列别名是嵌套结果的命门:两表同名列必须用别名分开,否则装配错乱
  • column 传参:主查询结果的一列成为子查询入参,可多列传参(column 写成 id_card_id,del 标记形式对应多参数)
  • 懒加载三件事:fetchType 局部开关、全局 lazyLoadingEnabled、会话必须开着才能补查
  • 选择口诀:列表嵌套结果、单条看情况、懒加载只在嵌套查询里存在

一对一缝合完毕,下一节难度升一级:一个用户名下多张订单,结果集的行会按主表行数翻倍展开——去重与分页的两个经典坑都在那里等着。


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