11.2 标识映射

在一个工作范围内让每个数据库身份只加载成一个对象,避免重复对象和相互矛盾的状态。

学习目标

  • 能解释标识映射如何保证同一数据库行在工作范围内只对应一个内存对象
  • 能比较标识映射与"每次都重新查询"两种方案在一致性和性能上的差异
  • 能对一道持久化场景走完"事务范围→身份缓存→对象访问→变更集合→提交"的判断链

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

先猜一猜:同一个订单对象,在事务前半段和后半段分别从数据库加载,如果两次加载之间数据发生了变化,你的程序会看到两个不同的对象吗?

面对的具体压力是"同一身份实例数、查询次数、脏对象集合与关联装载范围"。它采用的机制可以概括为:在一个工作范围内让每个数据库身份只加载成一个对象,避免重复对象和相互矛盾的状态。机制带来的收益必须与新增间接层、同步责任或迁移成本同时记录,否则学习者只会得到一个没有拒绝条件的模式名称。

对 11.2 标识映射,优先比较的替代路线是:根据领域复杂度选择入口、活动记录、映射器、及结构映射组合。本页的通过条件是"能证明同一标识的多次读取返回同一实例,并在工作范围结束时正确释放映射";若实验只能显示结果而不能指出 工作单元 从何处越界,就不能据此选择 11.2 标识映射。

目录单元到教学证据

标识映射解决什么问题

在面向对象的持久化场景中,同一个数据库行可能在同一个事务中被多次加载。如果没有标识映射,每次加载都会创建一个新的内存对象,导致两个问题:一是重复对象浪费内存,二是对一个对象的修改不会被另一个对象感知——这违反了"同一行数据在工作范围内只有一个表示"的语义。

是标识映射的核心机制:它维护一个从数据库主键到内存对象的映射表,每次加载数据时先查缓存,命中则直接返回已有对象,未命中才从数据库加载并加入缓存。

工作范围与释放时机

标识映射的生命周期必须与工作单元绑定。一个工作单元开始时创建标识映射,结束时释放。如果映射长期持有不释放,会导致内存泄漏和过时数据。决定了标识映射应该存活多久:短事务中映射随事务结束释放,长对话中映射需要配合并发控制策略。

唯一实例的保证

标识映射提供的保证是"同一个工作范围内,同一个数据库主键只对应一个内存对象"。这意味着如果你在事务开始时加载了 id=42 的订单,修改了它的状态,然后在同一事务中再次加载 id=42 的订单,你会得到同一个对象,修改状态立即可见。

三步交互实验

Identity Map:同一 ID = 同一实例请求序列load(id=42)→ 缓存未命中load(id=42)→ 缓存命中 ✓load(id=99)→ 缓存未命中IdentityMapMap<id, object>42 → Order@0x199 → Customer@0x2get(id): objectput(id, obj): voidremove(id): void核心保证✓ 同 ID 同实例✓ 无重复对象✓ 修改可见✗ 需手动清理✗ 内存占用作用域选择:• 严格模式:一个 UoW/请求一个 Map(推荐) • 全局模式:整个会话共享(需处理过期)Identity Map 确保每个数据库身份在作用域内只产生一个对象实例
Identity Map 用 Map 缓存已加载对象,同一 ID 的后续请求直接返回已有实例, 避免重复对象和相互矛盾的状态。

常见误区

专属设计案例:订单聚合持久化

把 订单聚合持久化 切成"事务范围 → 身份缓存 → 对象访问 → 变更集合 → 提交"五个观察点。11.2 标识映射 的设计草案必须写出谁拥有状态、谁作出业务决定、失败怎样传播,以及 工作单元、唯一实例、加载时机、写出顺序、并发 中哪个指标最先提示当前方案不再适用。

设计记录采用五个可换行字段:单元键为 poeaa24-pattern-10-identity-map;模式族为 mapping;裁决是"能证明同一标识的多次读取返回同一实例,并在工作范围结束时正确释放映射";观测项包括 工作单元、唯一实例、加载时机、写出顺序、并发;拒绝条件是"工作单元 超过团队为 11.2 标识映射 设定的边界"。

配置不是生产框架语法,而是一张评审卡。对 11.2 标识映射 的任何实现都要能把运行证据重新映射到这张卡;如果更换 ORM、Web 框架或部署平台后无法回答同一组问题,说明决定依赖的是工具偶然行为而不是模式语义。

选择与拒绝矩阵

评审问题选择 11.2 标识映射 的证据应拒绝或改用其他方案的信号
责任事务范围 到 提交 的所有者清晰身份缓存 可以绕过边界直接改写状态
变化工作单元 的变化被局部吸收一次小改动同时触及 唯一实例、加载时机、写出顺序
失败故障能在 提交 前被识别并回退只能看到最终错误,无法定位 工作单元 的首个异常
替代已与同族候选比较并保留撤回路径因框架内置或团队习惯而跳过问题分析

本章小结

  1. 标识映射保证同一个工作范围内,同一个数据库主键只对应一个内存对象。
  2. 身份缓存是标识映射的核心机制,维护从主键到对象的映射表。
  3. 标识映射的生命周期必须与工作单元绑定,工作单元结束时释放。
  4. 不带标识映射的多次加载会导致重复对象和状态不一致。
  5. 选择与拒绝矩阵帮助你在真实项目中判断是否需要标识映射。

本章练习

练习

问题 1: 在一个 Web 请求中,两次调用了同一个订单的加载方法,第二次调用时该订单在数据库中被另一事务更新了。如果使用了标识映射,第二次调用会返回什么?

问题 2: 标识映射和全局缓存有什么区别?什么场景下应该用全局缓存而不是标识映射?

问题 3: 团队说"我们不用标识映射,因为每次查询数据库都能拿到最新数据"。如何用选择与拒绝矩阵来评估这个决定?

出处声明

名词解释

名词解释

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

标识映射
在一个工作范围内维护从数据库主键到内存对象的映射表,保证同一行只对应一个对象。
工作单元
维护一个业务操作中所有受影响对象的列表,决定最终的写入顺序和提交策略。标识映射通常与工作单元配合使用。
身份缓存
标识映射内部维护的键值存储,键为数据库主键,值为内存对象引用。
事务范围
标识映射的存活时间,通常与数据库事务或工作单元的生命周期一致。
唯一实例
标识映射提供的保证:同一个工作范围内,同一个数据库主键只对应一个内存对象。

讨论

评论区加载中…