第12章 对象-关系结构模式

把对象身份、关联、值与继承结构映射到关系模式。

学习目标

  • 能解释对象身份、关联、值对象和继承如何映射为可查询且可演化的关系结构
  • 能用 TypeScript 把订单聚合映射为 Identity Field、Foreign Key 与 Embedded Value 的明确边界
  • 能根据独立生命周期、查询需求和对象图复杂度,拒绝或选择关联表、依赖映射与 Serialized LOB

为什么第12章对象-关系结构模式值得单独学习

第12章对象-关系结构模式的核心不是让 ORM 自动生成表,而是决定对象图中的哪些部分拥有独立身份、哪些部分依附父对象、哪些值应展开为列。订单聚合同时包含订单行、地址和标签集合;如果所有对象都塞进一列,查询和约束会消失,如果所有值都拆成独立表,又会制造不必要的身份和连接。

是结构映射的起点。本页以 2024 年中文版公开目录限定第12章的范围,并依据 Martin Fowler 的作者图书页和模式目录独立重写;目录只决定要讲什么,案例、实验、判断题和答案均为本课程原创。

先建立直觉:问题、机制与代价

对象关系结构需要先问“要不要独立查询和独立生命周期”,再选择表结构。适合可独立查询的子集合;适合关系需要独立扩展;适合地址快照等从属值。

能让常用查询保持简单;

只适合整体读写且查询需求很弱的边界。继承映射也应根据查询、约束和演化成本选择单表、类表或具体表,而不是由 ORM 默认值决定。

对第12章对象-关系结构模式,优先比较的替代路线是:先用 Identity Field 固定身份,再按关联类型选择 Foreign Key 或 Association Table,按生命周期选择 Dependent Mapping 或 Embedded Value,最后才评估 Serialized LOB。若实验只能显示对象被保存,不能指出查询路径、独立生命周期和约束归属,就不能据此选择结构映射。

目录单元到教学证据

第12章 对象-关系结构模式

第12章对象-关系结构模式要求把“对象图如何落到关系结构”变成可观察的架构决定:先标出标识、关联、生命周期、值对象和继承边界,再用订单聚合的查询、删除、更新和迁移测试验证决定。只有映射后仍能表达必要约束并支持真实查询,结构模式才不是表生成器的偶然输出。

专属代码案例:订单聚合的结构映射

把订单聚合持久化切成“Identity Field → Foreign Key → Embedded Value → Association Table → 查询投影”五个观察点。第12章对象-关系结构模式的设计草案必须写出谁拥有状态、谁作出业务决定、失败怎样传播,以及标识、外键、聚合边界、值对象和继承策略中哪个指标最先提示当前方案不再适用。

设计记录采用五个可换行字段:单元键为 poeaa24-chapter-12-object-relational-structure;模式族为 mapping;裁决是“独立生命周期用身份和外键,从属值展开或依赖映射,多对多关系使用关联表,复杂图只有在整体读写时才序列化”;观测项包括标识、外键、聚合边界、值对象、继承策略;拒绝条件是“为方便生成表而牺牲必要查询、唯一约束或独立生命周期”。

先预测:订单地址应该展开为订单表的列,还是成为独立地址实体?先写下地址是否独立复用、是否需要单独查询和是否随订单删除,再阅读实现;验证时要能说明 Embedded Value 与 Dependent Mapping 的边界,而不是只比较表数量。

type Address = {
  street: string;
  city: string;
  postalCode: string;
};
 
type Order = {
  id: string;
  customerId: string;
  shippingAddress: Address;
  tagIds: string[];
};
 
type OrderRow = {
  id: string;
  customer_id: string;
  shipping_street: string;
  shipping_city: string;
  shipping_postal_code: string;
};
 
type OrderTagRow = { order_id: string; tag_id: string };
 
function toOrder(row: OrderRow, tags: OrderTagRow[]): Order {
  return {
    id: row.id,
    customerId: row.customer_id,
    shippingAddress: {
      street: row.shipping_street,
      city: row.shipping_city,
      postalCode: row.shipping_postal_code,
    },
    tagIds: tags
      .filter((tag) => tag.order_id === row.id)
      .map((tag) => tag.tag_id),
  };
}
 
function toRows(order: Order): { order: OrderRow; tags: OrderTagRow[] } {
  return {
    order: {
      id: order.id,
      customer_id: order.customerId,
      shipping_street: order.shippingAddress.street,
      shipping_city: order.shippingAddress.city,
      shipping_postal_code: order.shippingAddress.postalCode,
    },
    tags: order.tagIds.map((tagId) => ({ order_id: order.id, tag_id: tagId })),
  };
}

这个映射把订单身份作为 Identity Field,把客户关系表达为 Foreign Key,把地址字段作为 Embedded Value,把标签集合表达为 Association Table。真实实现还要加唯一约束、外键删除规则、事务和增量更新;不要为了减少类或表而把需要独立查询的对象图塞进 Serialized LOB。

对象-关系结构决策树

结构映射的顺序是先确认身份,再判断关联是外键还是关联表,再判断从属值和复杂对象图。下面的决策树把 Identity Field、Foreign Key、Association Table、Dependent Mapping、Embedded Value 和 Serialized LOB 的入口放在同一张证据图中。

对象-关系结构模式:决策树要映射什么?身份关联从属/值复杂图Identity Field对象 ↔ 行 的 ID 桥梁几乎所有映射都需要多对多?Foreign Key一对多:FK 在子表最自然的关联方式Association Table多对多:中间表存对两个 FK 组成一行Dependent Mapping从属对象无独立 ID生命周期跟随父对象Embedded Value值对象 → 父表的列如 Address → 3 列Serialized LOB整个对象图 → 一列牺牲查询换取简单选择顺序:1. Identity Field(必选)→ 2. 关联类型决定 FK/AT → 3. 从属用 DM → 4. 值对象用 EV → 5. 最后才考虑 LOB结构映射的核心问题:对象的哪些部分需要独立的表行?
对象-关系结构模式解决"对象的哪些部分映射为独立的表行"。Identity Field 是基础, 关联用 Foreign Key 或 Association Table,从属对象用 Dependent Mapping, 值对象用 Embedded Value,复杂对象图最后才考虑 Serialized LOB。

决策树不是自动生成器:每个叶节点都要回到查询、约束、生命周期和迁移测试,确认选择没有把领域语义隐藏在数据库列或序列化字符串里。

结构映射对比图

面对地址、标签和订单行时,先比较独立生命周期与查询需求,再选择展开列、外键、关联表或序列化。越靠右的结构通常能保存更多对象形状,但可查询性、约束和局部更新会变差。

结构映射:独立性越强,关系结构越明确独立实体Identity Field独立主键 + 查询外键表达关联关系或从属值Association TableDependent Mapping约束生命周期只整体读写Embedded Value / LOB少查询、少约束迁移和大小有成本拒绝条件:结构映射牺牲必要查询、约束或独立生命周期先明确生命周期与查询,再决定展开、关联或序列化
对象结构映射必须保留领域真正需要的身份、关联、约束和查询能力。

选择与拒绝矩阵

评审问题选择结构映射的证据应拒绝当前方案的信号
身份主键、唯一性和对象生命周期清晰一个字段既当业务编号又当技术身份且无约束
关联一对多、多对多与关系属性分别可查询为多对多关系复制列或隐藏在不可查询字符串中
从属删除、更新和父子边界经过测试从属值意外拥有独立生命周期或无法级联清理
演化继承和序列化策略支持必要查询与迁移为减少表数而放弃约束、索引和局部更新

常见误区

可验证练习

练习

本组练习覆盖第12章对象-关系结构模式,并要求把标识、外键、聚合边界、值对象和继承策略映射到订单聚合持久化。

问题 1:映射地址。 订单地址只属于当前订单,随订单删除,不被其他聚合独立查询,应该优先比较什么?

问题 2:映射标签。 一个订单可以有多个标签,一个标签也可用于多个订单,且关系未来可能增加排序字段,应该怎么设计?

问题 3:评估 Serialized LOB。 订单历史快照只整体读取、不参与筛选和局部更新,可以直接序列化吗?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

标识字段

把对象或领域实体的稳定标识映射到关系表主键,使对象生命周期可以被定位的结构映射。

外键映射

把父表主键复制到子表作为关联列,以表示一对多或多对一关系的映射方式。

关联表映射

用中间表保存两个实体标识及其关系,适合多对多关系或关系本身带属性的映射方式。

嵌入值

把值对象字段展开到拥有者表列中,使值对象保持不可变语义但不拥有独立表行身份。

本章小结

掌握第12章对象-关系结构模式的标志不是记住表结构名称,而是能在订单聚合持久化中解释“Identity Field → Foreign Key → Embedded Value → Association Table → 查询投影”的责任链。学习者应利用标识、外键、聚合边界、值对象和继承策略作出可证伪的选择,并在失去必要查询、约束或独立生命周期时明确拒绝当前实现。

前后导航

来源与改写范围

资料与写作方式声明

本章以Martin Fowler《企业应用架构模式》与公开模式目录权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…