工作单元是什么
工作单元(Unit of Work)在一次业务操作期间跟踪对象的增删改,把变更暂存在内存里,到提交时统一计算变更集并写入数据库。它的两个直接收益是:事务边界由业务用例显式界定,而不是散落在各处的 save 调用里;同一对象的多次修改合并成一次写入,减少数据库往返。它常与仓储(负责存取)和标识映射(保证同一记录对应同一对象实例)配合使用。
基本信息
| 职责 | 记录新增/修改/删除,事务提交时统一持久化 |
|---|---|
| 配合模式 | 仓储(存取)、标识映射(同一对象同一实例) |
| 收益 | 事务边界明确,减少数据库往返 |
| 边界 | 一个工作单元对应一个业务用例,不跨越用户交互的等待时间 |
典型使用场景
- 下单流程:订单、订单行、库存扣减在一个事务内统一提交
- 批量导入:累积若干条记录后按批提交,控制事务大小与内存占用
- 领域模型持久化:对象图在内存中修改,提交时由变更跟踪生成 SQL
- 审计与事件发布:提交时把审计记录与领域事件一起落库,保证与业务变更同事务
- 测试替身:用内存实现替换数据库,验证业务逻辑而不依赖真实库
提交时机示意(伪代码)
提交时机示意(伪代码)
uow = new UnitOfWork()
order = repo.find(1)
order.addLine(item) // 跟踪修改
repo.save(order)
uow.commit() // 统一计算变更集并写库
// 业务失败:uow.rollback(),不产生部分写入常见实现方式
| 方式 | 机制 | 注意 |
|---|---|---|
| 显式登记 | 代码显式 registerNew/registerDirty | 最清晰但样板代码多 |
| 变更跟踪代理 | 框架代理对象记录属性变化 | 对象必须由仓储返回才被跟踪 |
| 脏检查快照 | 提交前对比对象快照差异 | 内存开销大,大对象图需谨慎 |
| ORM 会话 | 会话即工作单元,如 ORM 的 Session 或 EntityManager | 会话生命周期与事务边界容易混淆 |
使用要点
- 工作单元的生命周期与业务用例对齐:请求开始创建、请求结束提交或回滚
- 把审计记录与领域事件放进同一个工作单元,避免业务成功但事件丢失
- 绕过工作单元的直接写库操作会让变更跟踪与审计丢失,需要在代码评审中阻止
- 批量任务要分批提交:单个工作单元过大时内存与锁持有时间都会失控
工作单元跨越过长的业务流程会长时间占用事务与连接,应保持短事务;把“等待用户确认”之类的环节放在事务外,用状态字段而不是未提交的数据库事务来表达中间状态。
工作单元不等于“数据库事务”:它描述的是内存中的变更集合与提交时机,最终是否使用数据库事务、用到什么隔离级别,仍是单独的设计决定。