Day32-16|13.5.1 数据库设计与类的设计融合:让对象模型与数据模型协同演进

对应教材:《系统架构设计师教程(第2版)》第13章“层次式架构设计理论与实践”13.5“数据架构规划与设计”中的 13.5.1“数据库设计与类的设计融合”。本文面向软考高级系统架构设计师,重点学习面向对象分析与数据库设计的衔接、实体识别、关联映射、继承、聚合、事务、性能和模型演进。

一、教材定位与学习目标

软件系统常常同时维护两套重要模型:类模型描述对象、职责、行为和协作,数据库模型描述表、列、主键、外键和约束。两者如果完全割裂,开发人员需要反复转换,容易出现对象和数据含义不一致;如果强行一一对应,又会让领域模型被表结构绑架,复杂业务规则无法得到合理表达。

数据库设计与类设计融合,不是要求“一个类对应一张表”,而是要求在需求分析、领域建模、持久化设计和架构实现之间建立可追踪关系。架构师需要识别哪些类具有持久化身份,哪些只是值对象或服务;哪些对象属于同一一致性边界;数据库如何支持查询、并发、审计和生命周期;模型变化时如何进行迁移和兼容。

本篇学习目标:

  1. 理解类模型和数据库模型的目标差异与协作关系。
  2. 掌握从业务概念识别持久化实体、值对象和聚合的方法。
  3. 设计类与表、属性与列、关联与外键、继承与表结构的映射。
  4. 处理事务边界、并发、索引、查询模型、审计和数据迁移。
  5. 能够结合案例题和论文说明对象模型与数据库设计的融合策略。

二、类模型与数据库模型的目标差异

1. 类模型关注行为和职责

类模型回答对象是谁、拥有何种责任、如何协作以及如何保护业务规则。一个订单类可能负责添加明细、计算金额、提交和取消;一个审批服务负责协调多个对象和外部系统。类模型强调封装、抽象、多态和变化隔离。

2. 数据库模型关注持久化和查询

数据库模型回答数据如何保存、如何约束、如何查询、如何恢复和如何并发访问。它强调规范化、索引、主键、外键、事务和执行计划。数据库表不一定直接表达完整业务行为,数据库结构也可能为报表、搜索和历史追踪进行优化。

3. 融合的目标

融合是建立概念、身份、关系和生命周期的对应关系,同时允许两种模型保持必要差异。类模型要能够可靠地持久化,数据库模型要能够支持业务对象的加载和保存;双方通过映射、仓储、数据访问和迁移机制协作,而不是互相泄漏所有内部细节。

对比项 类模型 数据库模型
核心关注 行为、职责、协作 存储、查询、约束
身份 对象身份和生命周期 主键和唯一约束
关系 引用、聚合、多态 外键、连接表、关联查询
变化 业务规则和实现变化 表结构、索引和数据量变化
优化目标 可理解、可复用、可测试 正确、快速、可恢复

三、识别持久化概念

1. 持久化实体

具有业务身份、需要跨请求或跨系统保存的对象通常是持久化实体。例如客户、合同、订单、设备和账户。实体应有稳定标识、生命周期和状态变更规则,不能只从页面字段数量判断。

2. 值对象

地址、金额、时间段、坐标和联系方式等如果没有独立生命周期,可以建模为值对象。它们可以嵌入实体表、拆成从表或序列化保存,但应保持值语义和校验约束。

3. 临时对象和服务

命令、查询参数、计算结果、策略和领域服务通常不需要直接持久化。把每个类都设计成表,会产生大量无业务意义的数据结构和复杂加载关系。

4. 聚合和持久化边界

聚合根是外部访问聚合的入口,聚合内部对象可以在同一事务中保存。跨聚合关系通常保存标识而不是完整对象引用,避免事务和加载图无限扩大。数据库外键是否覆盖跨聚合关系,要结合完整性要求、分库和迁移策略决定。

四、类与表的映射

1. 一对一映射

简单实体可以一类一表,属性一列,标识对应主键。这种方式直观,但不能假设所有业务对象都适合一一映射。值对象、继承和聚合会产生更复杂结构。

2. 一个类对应多张表

复杂实体可能把基础属性、扩展属性、历史版本和大字段分开存储,以改善查询和写入性能。映射时要明确加载和保存边界,避免一次加载所有历史和大对象。

3. 多个类对应一张表

简单值对象或多个紧密对象可以共享一张表,但必须明确字段命名、并发和生命周期。若不同类拥有不同变化原因,共表可能使修改互相影响。

4. 属性与列

属性类型要映射到合适列类型,考虑长度、精度、时区、字符集、空值和默认值。枚举可以映射代码或字符串,关键是保持语义稳定。敏感属性应采用加密、脱敏或分离存储。

5. 关联与外键

对象引用对应外键或关联表,但关系的业务方向、可选性、删除和级联策略要明确。双向对象关联映射时要避免循环序列化和无限级联。大集合要采用分页、批量和独立查询。

五、继承与多态映射

1. 单表继承

父类和子类字段放在一张表,用类型字段区分。查询多态对象较方便,但会产生大量可空列,数据库约束和不同子类特有关系较难表达。适合类型稳定、共享字段多和多态查询频繁的场景。

2. 连接表继承

父类和子类分别建表,子表通过主键外键关联。结构规范,约束较清晰,但查询需要连接,写入和迁移更复杂。适合层次稳定、字段差异明显的领域。

3. 具体类表

每个具体子类独立建表,避免空字段,但跨类型查询和公共字段维护成本较高。适合多态查询少、具体类型独立性强的场景。

4. 组合优于继承

许多业务差异更适合使用类型、策略、配置和组合表达,而不是建立深层继承。继承映射一旦变化,表结构、查询和接口都可能受到影响。架构师要以业务稳定性和查询特点决定。

六、聚合、事务与数据库约束

1. 聚合内一致性

聚合内部需要满足的规则可以由对象行为、应用服务和数据库约束共同保证。数据库约束适合唯一性、非空、范围和外键等通用完整性;复杂状态规则应由领域对象和服务表达,不能完全依赖触发器。

2. 事务边界

一个用例可能修改多个表,但不必跨多个聚合和外部系统使用一个长事务。保存聚合和关键状态使用短本地事务,跨聚合通过事件、任务、补偿和对账协作。数据库设计要支持状态、版本、幂等键和操作记录。

3. 级联与删除

聚合内部的级联保存和删除可以简化实现,跨业务边界的级联删除则有风险。关键业务通常采用作废、归档和软删除,保留审计与恢复,而不是物理删除全部关联数据。

4. 并发控制

对象模型可以使用版本字段实现乐观锁,数据库在更新条件中检查版本;热点资源可以通过队列或悲观锁控制。类设计和数据库设计必须一致,否则对象认为更新成功,数据库却覆盖了他人修改。

七、查询、索引和读写模型

1. 面向对象加载与集合查询

对象导航适合领域操作,数据库集合查询适合列表、统计和报表。不能用一个对象加载策略解决所有场景。实体聚合加载要控制深度,列表查询应使用投影、专用查询对象和分页。

2. 索引由访问场景决定

索引不是从类属性自动生成即可。应根据查询条件、排序、连接、选择性、写入成本和数据分布设计。状态、租户、业务编号和时间往往需要组合索引,但要通过执行计划验证,避免索引过多拖慢写入。

3. 读模型和历史模型

报表、搜索和审计可以使用独立读模型或历史表,避免污染核心事务模型。读模型通过事件或批处理更新,要明确时效、重建、权限和一致性。类模型和数据库模型可以针对读写场景分别优化。

八、模型演进与数据迁移

1. 类模型变化

业务概念改名、拆分、合并或状态变化时,数据库不一定能直接跟着修改。应建立映射和兼容期,更新接口、事件、查询和历史数据处理方式。

2. 数据库迁移

推荐采用扩展—迁移—收缩的渐进方式:先增加新列或新表并兼容旧代码,再回填和双读/双写,完成验证后切换读取,最后删除旧结构。迁移脚本需要版本、校验、回滚或补偿和运行监控。

3. 历史语义

历史数据应保留生成时的规则、状态和版本。不能只修改表字段和类名而丢失当时的业务含义。审计和报表系统尤其需要保留快照或事件。

九、案例分析:制造企业设备维修系统

某制造企业设备维修系统原先以设备表、维修单表和人员表为中心,页面直接编辑表字段。随着设备类型增加,维修规则、备件、保养计划和故障历史逐渐复杂。系统出现设备状态非法、维修单与备件消耗不一致、历史记录无法解释和报表查询影响在线操作等问题。

1. 领域模型

设备是持久化实体,设备类型和位置可以是值对象或受控引用;维修单作为聚合根,维修任务、故障描述和备件消耗在一致性边界内;保养计划和库存属于独立聚合,通过设备编号和备件编号关联;维修策略由领域服务根据设备类型和故障等级决定。

2. 数据模型

设备表保存身份、类型、位置和当前状态;维修单与维修明细分表保存任务和消耗;设备状态历史采用追加表记录时间、操作者和原因;报表使用维修统计读模型。关键表包含租户或工厂、版本、审计和幂等字段。

3. 事务设计

创建维修单和明细在本地事务中完成;备件预留通过库存服务和事件协作;维修完成时校验状态和版本,再发布维修完成事件;报表异步更新。禁止页面直接把设备状态改成任意值。

4. 性能和演进

维修列表使用专用查询和组合索引,历史状态按设备和时间分区或归档;大附件独立存储;设备类型增加新属性时采用扩展表或版本化配置,不立即修改所有核心表;迁移过程中通过双读和对账验证。

5. 效果指标

通过非法状态数量、维修与备件差异数、列表响应时间、报表对在线事务影响、模型变更周期、历史可追溯率和并发冲突数评价改造效果。

十、综合知识练习与详细答案

练习一:单项选择

数据库设计与类设计融合的正确理解是( )。

A. 每个类必须对应一张表 B. 数据库结构必须完全服从类继承层次 C. 在保持必要隔离的同时建立身份、关系和生命周期的可追踪映射 D. 业务对象可以直接修改任意表字段

答案:C。融合不是简单一一对应,而是通过映射和边界让两种模型协同演进。

练习二:多项选择

聚合和数据库设计时应考虑( )。

A. 一致性边界 B. 并发冲突 C. 生命周期 D. 查询和索引 E. 只按对象引用图决定表结构

答案:A、B、C、D。对象引用图不能单独决定持久化边界和数据库结构。

练习三:案例题

某项目采用自动 ORM 生成表结构,所有实体之间都建立双向关联,并把对象图直接作为接口响应。运行后出现循环序列化、级联误删、查询超时和数据库迁移困难。请分析原因并提出改进方案。

参考答案:自动生成表结构没有结合业务边界、查询、并发和生命周期设计;所有双向关联造成加载图和序列化循环;级联规则未区分聚合内外,可能误删跨边界数据;实体直出暴露持久化关系和敏感字段;对象图与数据库结构强绑定,导致迁移困难。改进时应重新识别实体、值对象和聚合,明确聚合根和跨聚合标识;使用 DTO、查询投影和专用读模型控制接口输出;只在聚合内部使用受控级联,跨边界采用显式服务和事件;根据访问场景设计索引、分页和加载策略,解决 N+1 和大对象图;由架构和数据设计共同评审表结构、版本和迁移脚本;通过契约、性能、并发、删除恢复和数据迁移测试验证。

十一、案例题答题模板

遇到类设计、数据库映射、ORM和数据模型融合类题目,可以按以下顺序作答:

  1. 说明类模型关注行为职责,数据库模型关注存储查询,两者目标不同。
  2. 识别持久化实体、值对象、聚合、服务、DTO和查询模型。
  3. 设计身份、属性、关联、继承、表结构、外键和映射边界。
  4. 根据不变量、事务、并发和生命周期确定聚合与持久化边界。
  5. 依据查询场景设计索引、投影、分页、读模型和历史模型。
  6. 处理 ORM 加载、级联、缓存、版本和安全输出问题。
  7. 采用扩展、迁移、回填、双读/双写、校验和回滚完成演进。

十二、论文写作素材

开头素材

“某制造企业设备维修系统原先直接以数据库表驱动页面,业务对象、状态规则和数据结构没有清晰边界。随着设备类型和维修流程增加,系统出现非法状态、备件差异、历史不可追溯和报表影响在线交易等问题。为解决对象模型与数据库模型脱节的问题,我参与建立了实体、值对象、聚合和查询模型,并设计相应的持久化映射和数据迁移方案。”

正文素材

可以重点写四点:第一,如何从业务概念识别持久化实体和值对象,按照一致性和生命周期划分聚合;第二,如何处理属性、关联、继承、级联、版本和并发映射;第三,如何区分领域实体、查询投影、读模型和历史模型,根据访问场景设计索引和性能方案;第四,如何通过扩展—迁移—收缩、双读/双写、对账和审计完成类模型与数据库演进。要强调不是简单追求类表一一对应。

结尾素材

“实践表明,数据库设计与类设计的融合需要在业务语义、对象行为和数据访问之间建立稳定映射,而不是让任一模型完全支配另一模型。通过清晰的持久化边界、聚合一致性、查询优化和渐进式迁移,系统可以同时获得领域模型的可维护性和数据库运行的可靠性。”

十三、今日复盘

本节核心结论是:类模型表达行为和职责,数据库模型表达存储和查询;融合的关键是对齐身份、关系、生命周期和一致性边界,同时允许两种模型为各自质量目标保留差异。

请回答:

  1. 为什么一个类不一定对应一张表?
  2. 如何识别持久化实体、值对象和聚合?
  3. 聚合边界如何影响表结构和事务?
  4. 继承映射的三种方式各有什么优缺点?
  5. 为什么查询模型可以与写模型分开?
  6. 类模型和数据库模型变化时如何渐进迁移?

请以订单、合同或设备维修系统为例,画出“业务概念—类/聚合—映射—表/索引—查询模型”的关系,并标注事务、并发版本、审计、历史、缓存和迁移位置。完成后,你就掌握了 13.5.1 的核心内容。