O/R Mapping中对象关系映射解决方案汇总

对于一个 ORM 方案时,为了提供用户对具体映射方案的细颗粒度的控制的可能,特别对于企业级应用,应该是用户选择是否使用该产品比较重要的因素之一,需要尽可能多的实现各种可选的常用映射方案,本文的目的就是对 O/R Mapping 中对象关系映射方面的解决方案作一个汇总,当设计或评价一个 ORM 方案的时候,也可以相应的以此作为参考。

1、  继承

posted on 2005-07-21 13:42 Teddy's Knowledge Base 阅读(6314) 评论(22)  编辑 收藏 引用 收藏至365Key

评论

 

   FYI:

继承关系有三种映射方式:

l         整个类层次结构使用一个数据实体:将一个完整类层次结构映射成一个数据实体,而层次结构中所有类的所有属性都存储在这个实体中。这种方法的优点是简单,因为所需的所有数据都可以在一张表中找到,支持多态性,并且使用这种方法,专门报告(为一小组用户特定目的所执行的报告,这些用户通常自己写报告)也非常简单。缺点是每次在类层次结构的任何地方添加一个新属性时都必须将一个新属性添加到表中。这增加了类层次结构中的耦合- 如果在添加一个属性时有任何错误,除获得新属性的类的子类外,还可能影响到层次结构中的所有类。另外,此种方式数据库冗余较大,可能浪费数据库中的许多空间。

l         每个具体类使用一个数据实体:每个数据实体就既包含属性又包含它所表示的类继承的属性。这种方法最大的好处是,它仍然能相当容易地执行专门报告,只要您所需的有关单一类的所有数据都只存储在一张表中。但也有几个缺点。一个是当修改类时,必须修改它的表和它所有子类的表。第二,无论何时,只要对象更改了它的角色,则需要将数据复制到相应的表中,并为它指定一个新的OID。这又涉及到很多工作。第三,很难在支持多个角色的同时仍维护数据完整性。

l         每个类使用一个数据实体:为每个类创建一张表,它的属性是 OID 和特定于该类的属性。这种方法的最大好处就是它能够最好地适应面向对象的概念。它能够很好地支持多态性,对于对象可能有的每个角色,只需要在相应的表中保存记录。修改超类和添加新的子类也非常容易,因为您只需要修改或添加一张表。这种方法也有几个缺点。第一,数据库中有大量的表 -- 实际上每类都有一个(加上维护关系的表)。第二,使用这种技术读取和写入数据的时间比较长,因为必须访问多个表。如果通过将类层次结构中的每个表放入不同物理磁盘驱动器盘片(假设每个磁盘驱动器磁头都单独操作)上来智能地组织数据库的话,就可以缓解这个问题。第三,有关数据库的专门报告很困难,除非添加一些视图来模拟所需的表。

比较映射继承的各种办法:

考虑因素

每个层次结构一张表

每个具体类一张表

每个类一张表

专门报告

容易

中等

中等/困难

实现的难易程度

容易

中等

困难

数据访问的难易程度

容易

容易

中等/容易

耦合

非常高

数据访问速度

中等/

对多态性的支持

中等

所以在设计的时候需要自己权衡使用某种方式。

  
回复   

 

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 17:52 Teddy's Knowledge Base

我提到的三种关于继承体系的映射方案基本和james补充的一致,不过,很明显,你这个是直接拉的老外的文章的翻译版本,而且,坦率地说,翻译得真拗口!

一般来讲,对于一个优秀的orm方案,应该尽量都实现,然后,由用户根据实际情况对不同的对象综合考虑各种因素,选择最合理的映射方式,并尽量做到映射方式的变更对业务逻辑透明~~  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 17:54 双鱼座

哦,此文已经认真阅读过了。

大体上同意一部分解决方案,但是我觉得不是全面的解决方案。
1.关于继承、合成、聚合和关联这四种逻辑实体关系,我认为与数据库基数关系(一对一、一对多和多对多)存在一定差异,好比将不同层次的东西放到并列的位置。映射到数据库中的方式,我推荐使用“键关系”方案,避开对基数关系的考虑。不知你是否发现以下规律:
a.当实体A的主键列刚好一个,且该主键引用自实体B的时候,这种关系是A继承自B(其实就是你的方案1.1和2.4,以及James的方案三);
b.当实体A的主键列是两个,且分别引自实体B和实体C的时候,这种关系是A关联B和C;与关联的差异是,因为需要一个独立的实体来承载这种关联,所以Hibernate建议改成两个one-to-many,但是我觉得这样会掩盖真正的实体关系(就是你的方案3.2);
c.当实体A的非主键列中有一个引自实体B的时候,这种关系是B聚合A,如果这种引用是强制性的,则是合成关系,否则为聚合关系。是否为强制性,只需要将引用列设为非空即可;就是你说的1-*和0~1-*的关系(就是你的方案2.1)。
2.关于实体内关系(自引用),你没有讲到。不过这个有非常成熟的数据库解决方案。
3.所谓“组件(Component)映射”,听人提起过,但表示反对。因为这样很不利于映射成Sql语句来快速访问。如果确需要建立这样的关系,完全可以在数据库层以外来实现。
4.James列表中的考虑因素,我觉得实现的难易程度和数据访问的难易程度是不应该考虑的,因为这是一次性投资。  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 17:54 Teddy's Knowledge Base

以下这个链接是james给出的翻译的原文,建议大家看原文,文笔流畅多了~~

http://www.agiledata.org/essays/mappingObjects.html  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 18:01 双鱼座

>>一般来讲,对于一个优秀的orm方案,应该尽量都实现,然后,由用户根据实际情况对不同的对象综合考虑各种因素,选择最合理的映射方式,并尽量做到映射方式的变更对业务逻辑透明~~

不能支持以上说法。orm方案有一个功能是Transparent Persistence,数据库映射方案应该提供综合性能最好的方案,而不是所有的方案。并不是提供的方案越多就越好,这样反而使问题复杂化,令使用者不知所措。  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 18:03 idior

check this about hibernate.
http://www.alphatom.com/content/view/267/69/  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 18:03 James

@Teddy's Knowledge Base :

我只不过拿给你参考罢了——完全出于共享的目的。不管是谁总结的还是谁翻译的,不管里面的词汇用得好不好,我想能让人明白就行,达到知识共享的目的就行。

说实话,我还真没有看过你给的链接里面的内容。  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 18:07 Teddy's Knowledge Base

@双鱼座:

实际上,“继承、合成、聚合和关联”或者“一对一、一对多和多对多”是对实体关联的两种分类方式,前者更大程度上对oo语意有意义,后者则相对来说和数据库结构更相关,不知你是否同意?

另外,关于非二元关联,一般在设计时不被建议,有基本的数据库设计规范来转换成二元关联,如增加表示关联的实体等,我认为这是数据库设计的范畴,如果在映射模型本身也把这种多元关联直接考虑过细,我觉得未必好~~

关于你讲到的b,c两条,即将这种非主键的“关联”套用到“合成”、“聚合”这些概念,我到没有这样思考过,不知你能否给出点和这个相关的数据表和实体类的范例,举例说明一下你考虑到这种关联的好处?  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 18:11 Teddy's Knowledge Base

@james:

感谢你共享的资料,我的评论没有任何恶意,不过,因为我先看了我上面给的链接,再看你的资料,确实感觉你贴的这部分内容很相近,你可以看一下这篇原文,再作评论,我就是喜欢实话实说,真的没恶意,请见谅,不过说实话,我真的觉得这篇原文是很不错的~~  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 18:15 Teddy's Knowledge Base

@双鱼座:

我觉得,能给用户尽可能多的细颗粒度的控制,总是一件好事,当然并不绝对,但一般来讲,谁不喜欢多点选择呢?至于说让使用者感觉难使用的问题,这个,一方面又文档来说明,再者也应该给出默认的推荐实现的,这样高级用户可以细颗粒控制,初级用户也不至于无从下手~~  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 18:18 Teddy's Knowledge Base

@idior:

写本文时就参考了Hibernate的实现,应该说,现有的实现中,Hibernate是相对考虑得比较多了的,nhibernate如果跟上了hibernate的步伐,那么,基本思想应该一致~~  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 18:20 Teddy's Knowledge Base

@双鱼座:

关于自关联,其实我也提到了,见“一多一、一对多关联”后的括号,没有提出来讲是因觉得,基本上和非自关联差不太多,不知你有什么不同的意见?  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 18:23 双鱼座

 

实际上,“继承、合成、聚合和关联”或者“一对一、一对多和多对多”是对实体关联的两种分类方式,前者更大程度上对oo语意有意义,后者则相对来说和数据库结构更相关,不知你是否同意?
这个地球人都知道呀,但是你的文章是将两种不同分类方式的条目并列呀,1是继承2是一对一一对多3是多对多。
  另外,关于非二元关联,一般在设计时不被建议,有基本的数据库设计规范来转换成二元关联,如增加表示关联的实体等,我认为这是数据库设计的范畴,如果在映射模型本身也把这种多元关联直接考虑过细,我觉得未必好~~
关联关系明显有别于双重的聚合关系。推翻二元关联等于推翻UML,这个与数据库设计无关,但在数据库中保持与关系模型的同步体现了一种对称的意义。事实上,象用“用户组成员”或“用户所在用户组”来关联“用户”和“用户组”,最好的方式就是二元关联,否则会将这种多元关系隐藏起来,后患无穷。
关于你讲到的b,c两条,即将这种非主键的“关联”套用到“合成”、“聚合”这些概念,我到没有这样思考过,不知你能否给出点和这个相关的数据表和实体类的范例,举例说明一下你考虑到这种关联的好处? 
b是关联,不是聚合。例如,合成关系导致级联删除,而单纯聚合只会级联清空。在数据库设计一层还有大量细节来处理这两种不同的聚合类型。
   回复   

 

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 18:27 双鱼座

我从来看不上NHibernate这样的垃圾。这种无聊的移植简直让人不堪隐忍。建议大家如果有机会多多研究和学习ECOII和XPO这样的大师级的产品,会收益非浅。  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 18:28 Teddy's Knowledge Base

@双鱼座:

"这个地球人都知道呀,但是你的文章是将两种不同分类方式的条目并列呀"----你说得没错,但是,继承这种关联实在是和其他的二元关联区别很大,不提出来说不行啊~~我这篇文章的主要还是为数据表与实体类的映射作思考,所以尽可能站在数据库的角度来考虑了~~  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 18:33 Teddy's Knowledge Base

XPO听过,ECOII第一次听说,有空研究下,看有没有什么可以借鉴的思想~~  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 18:33 双鱼座

“自引用(Hierarchical)”与“聚合”完全不同,在处理细节上相差明显。我之所以专门作为一种关系来单独处理,一是因为越来越常见;二是绝大多数数据库本身不支持;三是自引用中有一个对根的处理的细节是聚合不需要考虑的;四是聚合没有次序依赖,而自引用有明显的冷依赖;五是聚合不太容易形成循环引用,即使形成也非常直观,但自引用则不然,很可能绕一大圈回来又引用到某个被引用者,在元数据层极难控制。  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 21:13 Teddy's Knowledge Base

 

@双鱼座:

非常感谢你的指点,从单位回家路上的某一瞬间,突然想明白了你说到的“级联删除”和“级联清空”的区别,突然感觉自己在很多方面都豁然开朗了。

还得从我前两天的 一篇文章说起,在那篇文章中,我其实是从Model的角度在看orm,而今天的这篇文章则是从关系数据库的角度在看orm,如果比较的话可以发现,前者,很自然的从继承、合成、聚合等这些角度来在思考,而本文则同样很自然的从一对一、一对多这样的角度在考虑,我在写这篇文章之时也确实觉得两个角度有点对不起来,因此颇有疑惑,不过,经你这一说似乎就有点明白问题所在了。

我这里先抛开元数据的复杂度,只从实体类和数据表关系的对应和需要实现的默认事务的角度,来重新理一遍对orm的理解:

如果只从数据库表结构和表间关系的方面来讲,我想,本文所列的几种对应方式应该大致包括了常见的实体类间的关联,只不过,对于继承(及其不同映射方案)、聚合、合成、自关联、次序依赖这些更大程度上属于Model范畴的关联,虽然,可能在映射到关系数据库表时都是相似的一对一或一对多的表结构和表间关系,但是,针对他们需要实现的,真正表现这些关联所表示的语义的默认的自动级联操作,或者说默认事务的需求是本质不同的。而正是通过这些不同的级联操作的区别,终于使我能够将Model视角和数据库视角真正联系起来了。

当然你也提到了不属于这种级联操作范畴的元数据复杂性以及自关联可能带来循环引用等问题,不过,我个人觉得,元数据复杂性某种程度来讲可以先不考虑,这可以通过改进元数据的描述结构以及更多文档和默认属性来解决,而循环引用的问题,某种程度来讲,我觉得也是需要的,比如,类A的实例a包含一个类型为A的属性a',而a'同样指向a表示的数据本身,当然这个例子比较极端,但在某些情形的逻辑下未必就不可能出现,如果,取数据的过程本身就是lazy read的话,我看似乎不是问题。
 
撰写本文的另一个目的,其实是为 LiteMda的实现进行的思考,这里再说一下具体实现方面的一点想法,想听听你的看法:
 
首先,对于模型和数据表的映射,将基本按照本文所述的方案进行映射,而将这些默认事务需求分离出来自动生成一组xml配置文件,描述需要进行级联操作的methods;
 
接着,数据持久层的类调用专门的执行器解析这些xml,并实现默认的事务;
 
在持久层之上的service层,允许通过持久层暴露的接口对默认事务透明地使用持久类的method,再向上层暴露一组业务类接口;
 
在业务类接口之上,再提供一组xml配置文件,用以实现附加的业务需要的事务以及自定义的aop需要,再由UI层通过专门的执行器解析这组xml,从而完成调用。
 
基本的调用过程就是这样,关于以上过程,我有两点还不太确定:

1、我在想,如果我在framework中完全不提供在代码中编码实现事务的接口,而强迫用户将所有的需要事务支持的操作,都必须在xml配置文件中定义的话是否合理?因为,如果,允许这样的事务,很可能会和配置文件中的事务发生冲突,造成死锁,并且这种死锁将非常不易调试;

2、再有,不知道你有没有注意到,我在两个层次(持久层和业务层)都提供了描述事务的xml配置文件,但是,是不是可能,当执行业务层之上的一个事务的时候,某个业务类的函数调用的持久层的函数内部也作了一次事务,则也可能发生潜在事务冲突和死锁,为解决这个问题,我能想到的办法就是让持久层之上的执行器和业务层之上的执行器通过同一个事务工厂来构造事务的开始,从而可以在运行时,一致的管理属于同一个事务的业务层methods和持久层methods的调用,不知你有什么关于事务的更好的方案?
   回复   

 

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-22 00:52 idior

为什么要在persistence layer加事务?业务层足以。
一般都是hibernate(persistence layer) 提供事务支持,
然后在business layer使用事务。spring使用配置文件对事务提供简化操作。castle也提供事务支持。  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-22 08:46 Teddy's Knowledge Base

@idior:

此处persist layer的事务使用于透明的实现包含各种关联的orm,而业务层的事务是只和业务相关的,概念上完全不同,不宜放在一起,并且,业务层封装调用persist layer,因此,业务层的executer不能直接访问persist layer的接口的,结构上也不宜一同处理两种事务。  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-22 09:30 双鱼座

关于事务,我尚不能推断你的Executer以职责以及如何工作还有如何将业务层的事务(大粒度的事务)与处理关系的事务(小粒度的事务)完全分开。所以事务相关的内容我认为应该提供一个事务上下文,将逻辑上的事务与物理上的事务统一起来。

对于Lazy Load我有不同的看法。在.net类库中通过C#实现Lazy Load是非常轻松的事情,只要做好对线程的控制,通过属性来实现还是非常简单的。但是我不认为这样更好。在我的心目中,数据库连接的代价比较高,我宁可在一个数据库连接中将关联的东西一次性Load出来,而不是先用一个连接Load完Master再立即用另外一个连接Load Detail。在我看来,既然存在关系,就一定存在时间和空间上的不平衡。访问Master立即访问Detail的可能性也许在90%以上,为了节省这10%的冗余而建立另外一个数据库连接往往是很不合算的。当然可以通过配置来控制哪些是Lazy Load哪些是立即Load。但是只要有任何一个地方采用了立即Load,你就必须按立即Load来思考而将Lazy Load放到一边。

你那篇关于关系的文章我刚刚才看到,描述得比较全面。  回复   

# re: O/R Mapping中对象关系映射解决方案汇总 2005-07-22 11:26 Bruce

@all 问个问题为什么一个业务实体只对应一张表?  回复   

  #  re: O/R Mapping中对象关系映射解决方案汇总 2005-07-21 17:39 James

 

说明:

对于比较完善的继承关系一般会有如下共有特点:首先,只允许一个非接口类型的父类(虽然有的程序语言支持从多个这样的父类继承,但一般不被推荐),同时允许多个接口类型的父类;其次,父类有可能为抽象类(即不能实例化,而只能被继承)。

方案:

一般,既然接口或抽象类不能被实例化,自然也不需持久化,因此,映射中只需考虑能够实例化,也即能够持久化的实体类,对于这些实体类,具体的常用映射方案有三:

1)  每个继承体系用一张表存储,该表包含整个继承体系中所有类的属性集合,并且每行包含一个额外的、用以识别该行代表的被持久化的实体类类型的列;

2)  每个实体类映射到一张表,并且,每个子类对应的表重复包含其所有层级的父类的属性,属于一个继承体系中的每张表包含相同的主键值以表示同一实例,并且此时,你有两种选择:一、当向子类对应的表插入一条记录的同时,需要向其所有层级的父类插入记录,以维持该继承语意,这样当取子类数据时,不需链接其父类对应的表,二、当向子类对应的表插入一条记录的同时,不向其所有层级的父类插入记录,但是,在取一个类的数据时,需要链接其所有的子类对应的表,以取得所有语意上属于该类型的数据,一般,对于可以选择用一个专用的继承关系查询表来存储所有的继承关系;

3)  每个实体类映射到一张表,但是,每个子类对应的表不重复包含其所有层级的父类的属性,而是,只保证属于一个继承体系中的每张表包含相同的主键值以表示同一实例,这时,子类的主键,同时也是对应到其父类的表的外键,当需要取得子类数据时,需要链接其所有层级的父类对应的表,好处是可以减少方案二中的数据或表结构的冗余,但是,此时,需要在取子类数据时链接子类的所有父类对应的表,一般,可以选择用一个专用的继承关系查询表来存储所有的继承关系。

2、  一对一关联、一对多关联(包含一对一和一对多的自关联)

 

说明:

所谓一对一关联,实际上还可以分为三种情形,即0..1 - 11 – 11 – 0..1三种情形;而一对多关联则分为* - 11 - *

方案:

一对一或一对多关联的实现一般比较简单,以下三种方案中第一种为最常用的映射方案,二、三、四则是两种在某些数据较简单的情形下可参考的方案:

1)  最常用的方案为为需要其他对象引用的类对应的表增加一个到被引用对象对应表的外键即可,只不过,与表对应的实体类代码中,对于一对多情形下的“多”这一端,需定义成集合类型;

2)  第二种是在Hibernate提出的所谓“组件(Component)映射”,举例来说,假如Person类包含一个Address成员类型的属性,而AddressCityStreetZipCode三个成员属性组成,假如Address除了与Person关联不被其他对象使用,则我们可以考虑只用一张数据表Person来持久化PersonAddress这两张表,Person数据表包含Person类中除Address的属性和Address类中的所有属性的集合,当然,这时需要在元数据中特别指明映射关系;

3)  还有一种针对上面的方案中的PersonAddress两个类的持久化方案则是将Address类型的所有属性先序列化,再存入Person表的字段Address中,这样也可以只用一张表来持久化两个类,当然,本方案中这种被序列化对象成员数据量应尽量小;

4) 在Hibernate中还提到了一种共享同一主键值的一对一关联。实际上,这种映射方实感觉只用于一种比较特殊的情形,就是,将原本可以同属于一个表中相对使用不太频繁的字段提出来放在另一张表中,这样,这两张表的记录就可以通过一个相同的主键进行关联,更多说明见Hibernate的文档。

3、  多对多关联(包含多对多的自关联)

 

说明:

所谓多对多关联自然就是* - *这种情形了。

方案:

对于多对多关联的实现一般有两种可选方案:

1)  类似一对多情形中的最常用方案,为关联的双方增加到对方的外键,该方案的好处是取数据时不需链接操作,只要读一张表就行,操作比较简单,缺点是会造成数据冗余;

2)  新增一张包含关联双方主键的关联表,这时,在取数据时,需要链接该关联表和数据表,优点是没有数据冗余,缺点是带来了一定的时限复杂度。

//文章结束

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值