无知的 tonyseek

Yet Another Seeker

对象-关系映射的外键映射问题

最近读 Martin Folwer 的《企业应用架构模式》,其中对于关系型数据库与对象模型的同步问题,提到了使用 DataMapper 和工作单元来协调。

而关系型数据库的数据储存和对象模型的差异在于,关系型数据库是不允许集合类型存在的。在典型的 1 - N 关系模式中,是由 N 来保存对 1 的引用(数据库中体现为外键);而在对象模型中, 1 - N 的聚集关系是由 1 来保存 N 的集合。也就是说,数据映射层的一个职责,是完成这个“翻转”过程。

完成这个“翻转”并不复杂,我的做法是在 DataMapper 中保存一个哈希表,用于记录外键值(也可以保存在领域对象中,但我设计 DataMapper 时尽量不入侵领域对象,对领域模型层保持最小获知)。然后在工作单元提交同步请求时,将各个对象内部的集合同步到这个哈希表中。最后在数据库操作时简单的从该哈希表得到外键值写入即可。

这种做法看似简单,但有一个致命的缺陷。问题就出在工作单元。工作单元在一个对象从数据库进入上下文的时候(find 阶段),是为这个对象克隆了一个副本作为原型保存的。工作单元提交时,将各个对象和它的原型对比,据此确定对象是否已被修改(dirty 对象),如果是 dirty 则交给对应的映射器执行 update 。

图片-1

但正如上图,在 A 聚集 B 的情况下,如果 A 中聚集 B 的集合发生修改,则 A 会被标记为 dirty。但此时对 A 执行 update 并不能真正保存集合的修改,因为数据库中外键是属于 B 的。也就是说,被标记为 dirty 的 A 并不足以表示 A 是否真的需要同步,而真正需要同步的 B 却不能正确的收到同步信息。

类似的问题还有 N - N 模式下,多对多的对象关系。在关系型数据库中,这要借助中间表保存信息。而如下图:

图片-2

无论是 A 还是 B 被标记 dirty,对 A、B 的同步并不能记录下信息,真正需要的是同步中间表。

种种麻烦,根源均在于“外键”的特殊性。从外键到集合,或从集合到外键,是我们的 ORM 需要“映射”的地方。所以,我想比较妥当的解决方案是记录下“外键”。也就是在数据表的元信息中标记出外键,以便识别做特殊处理。

而在工作单元提交同步请求的时候,应该先将所有涉及对象执行“同步外键”的操作,将关系模式转换。而在标记 dirty 的部分,应该把待同步对象做一个副本,unset 或用空数组覆盖掉其中集合性质的属性,然后再做对比,避免因集合类型的改变而把不该标记为 dirty 的对象标记为了 dirty;再取出元信息中所有外键,检查其值是否修改。将两批检查结果做 AND 运算,得出真正的 dirty 集合。再行进行数据库同步。

这种处理思路是:先转换模式(映射),再检查 dirty,最后同步。

另附上工作单元对于新对象、clean 对象、dirty 对象处理原理:

图片-3

Comments