关于O/R Mapping还是R/O Mapping的思考,我们应该选择什么?

ilovesijia 2010-03-15 11:37:27
最近对ORM方面的东西感兴趣,有一些想法,想和大家讨论一下。

我觉得Hibernate应算算是一个经典且功能强大的ORM框架。

它最大的有点是,你可以完全用面向对象分析与设计(OOAD)的思想来设计好所有的类,然后该框架就可以以非常人性化的方式为你获取对象或者持久化对象。

但同时我也发现它的一个缺点,就是当对象与对象之间的关系比较复杂的时候,ORM配置文件会很复杂,且比较难维护。并且当我们要对多个表进行关联查询时,虽然框架提供给了我们很多方便的接口让我们可以通过设置对象的属性的方式来告诉框架我们需要什么数据,然后框架会自动为我们生成一个复杂的SQL语句,最终返回给我们数据,但我发现当表关联较多并且数据量比较大的时候,往往性能不佳。

仔细想了之后,我觉得Hibernate的设计者的初衷是好的,就是想让我们尽可能的不用去关心数据库,让我们可以完全以面向对象的方式去实现我们的业务逻辑。但就我个人观点,我觉得它为我们做了太多的事情,比如一些原本属于业务逻辑的工作也由它帮我们自动完成了。比如订单(Order)和订单明细(OrderItem),一个Order包含了多个OrderItem,当我们要删除一个Order时,Hibernate能帮我们自动删除该订单下的所有OrderItem(当然你也可以选择不级联删除)。再比如,当我现在要显示一个帖子的详细页面,该页面需要显示帖子(Thread)内容以及所有的回复(Posts),如果用Hibernate,我们只需要查询一个Thread即可,而它下面的所有的Posts会自动被级联查询出来(因为我们已经在配置文件中配置好了,选择了非LazyLoad的情况时)。当然,你或许会觉得这样不是很好吗?全部自动化,你就很省心了,不是吗?确实,当业务逻辑比较简单时,这样没什么问题,但如果当业务逻辑比较复杂时,我们还能通过简单的不用动脑子的方式来配置映射关系,并且用简单的方式把数据查询出来吗?比如下面这个例子:

我现在要查询出一个论坛中某个版块下具有某种角色的所有用户,如果我们把这个角色理解为“版主”,那么就是相当于要查询出论坛中某个版块的所有的版主。
我可能会建如下一个表:

CREATE TABLE [tb_SectionRoleUsers](
[SectionId] [int] NOT NULL,
[RoleId] [int] NOT NULL,
[UserId] [int] NOT NULL
)

SectionId表示版块ID,RoleId表示角色ID,UserId表示用户ID。对于这个表而言,必须是以两个字段组合去查第三个字段才有意义。比如查某个版块下具有某个角色的所有的用户,或者查某个版块下某个用户具有哪些角色。而如果仅仅根据一个字段作为查询条件进行查询是没有意义的,如查询某个版块下的所有的角色及用户,没有意义。

假设我们的用户表是下面这样的:

CREATE TABLE [tb_Users](
[EntityId] [int] IDENTITY(10000,1) NOT NULL,
[NickName] [varchar](64) NULL,
[AvatarFileName] [varchar](128) NULL,
[AvatarContent] [image] NULL
)


那么对于“查某个版块下具有某个角色的所有的用户”这个需求而言,我们需要的是关联上面这两个表,并且以SectionId和RoleId作为查询条件,查询出tb_Users表中的相关记录。

对于上面这个相对复杂的需求,我们该怎么利用Hibernate来配置映射关系并且该怎么样来设计对象呢?我相信Hibernate肯定可以,但估计要比我之前举的例子要复杂难懂的多吧。因为这里大概涉及到三个原子对象(Section、Role、User),并且这三个原子对象可能会两两进行组合,并且都是M:N的关系,并且必须要同时根据两个条件进行查询才有意义。

经过我的思考,我觉得之所以我们会觉得麻烦是因为我们把相当一部分业务逻辑都交给Hibernate来完成了,Hibernate帮我们做了非常多我们觉得很简单,而要告诉Hibernate该怎样做却是很麻烦的事情。其实tb_SectionRoleUsers表是一个关系表,在Hibernate中是不会有关系对象这个概念的,对象与对象之间的关系Hibernate可以帮我们自动维护,我们无需关心。而我们现在的需求就是想简单的将一个关系表和一个用户表关联,然后查询出符合条件的数据而已。

我觉得无论是表之间的各种关系还是对象之间的各种关系,都属于业务逻辑范畴,这些关系的实现不应该交给ORM框架自动完成,而应该由我们来自己完成比较好。

另外一个我发现的规律就是,数据库是关系型数据库,也就是说数据库里所有的表中存放的数据是没有直接关系的,可以说是扁平状(平行状)的,虽然有一些外键的约束,但我们不能认为两个表有外键约束就认为他们是一个包含的关系,最多是一个引用的关系。所以,表与表之间都是平行的关系,而非树状关系。但纯面向对象的思想则不一样,对象有包含或聚合的关系,可以说是一种具有多个跟节点的树状关系。这是两种完全不一样的存储数据方式。Hibernate的做法是,数据库这边没有做任何的妥协,对象这边也没做任何妥协,因此导致框架为了转换这两个存储格式完全不一样的数据必须要做非常复杂的映射;既然这样,我们为什么不同时稍微改变一下这两种数据格式呢,让数据库提供一些有关系的“东西”出来,比如建几个视图;而对于对象,不需要完全按照面向对象的思想来设计对象,而是也设计一些扁平状的对象出来,也就是说各个原子对象之间不明确知道它们之间的具体关系,当我们需要连接两个具有某种关系的对象时,我们可以像数据库的视图那样,设计一些组合对象出来,并且所有的这些原子对象以及组合对象也和数据库一样,是扁平结构。这些组合对象用于和数据库的视图建立映射关系。这样一来,对象和数据库两方都做了一些让步,但换来的结果是数据存储格式的统一,这样的好处就是ORM框架在也不用这么复杂难懂了。我觉得这样不是满好的吗?并且我觉得这样的设计到更有点像ROM了,因为我们的思路已经转到了以数据库为基准,然后稍微修改一下对象的设计,从而达到一个数据存储格式的一致。然后ROM框架基于这个前提,为我们提供一些实用的接口,为我们实现CRUD操作。相信这样的ROM框架的映射文件也是简单并且很好理解的。

好了,就这些吧,大家觉得我的想法如何呢?欢迎大家批评指正。
...全文
121 13 打赏 收藏 转发到动态 举报
写回复
用AI写文章
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
如一宝宝 2010-03-16
  • 打赏
  • 举报
回复
ORM只不过是对DBHelper进行封装的而已
ilovesijia 2010-03-16
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 sp1234 的回复:]

而关系树由于根本无需面向对象所以最多就只能 --> 而关系数据库由于根本无需面向对象所以最多就只能


有趣地,你把Hibernate认为是O/R Mapping,于是认为它过分复杂和缓慢;而我认为它只是一个R/O Mapping,根本达不到一个O/R Mapping的灵活性,所以它才用起来过分复杂和缓慢的!
[/Quote]

你好,我得去上班了,由于是外企,所以上班时不能随便逛论坛。我下班后再回复你。你的回复给我很多启发。谢谢。
gsq_0912 2010-03-16
  • 打赏
  • 举报
回复
學習中。。。。。。。。。。
  • 打赏
  • 举报
回复
而关系树由于根本无需面向对象所以最多就只能 --> 而关系数据库由于根本无需面向对象所以最多就只能


有趣地,你把Hibernate认为是O/R Mapping,于是认为它过分复杂和缓慢;而我认为它只是一个R/O Mapping,根本达不到一个O/R Mapping的灵活性,所以它才用起来过分复杂和缓慢的!
  • 打赏
  • 举报
回复
对我来说,大多数ORM的最严重问题是它无法支持多态。例如查询:
var query=from Person p in db 
where p.Name.StartWith("sp")
select p;

如果有一些对象是从Person继承的呢?(可以想见我的项目中几乎所有人员对象都是Person的子类。)
如果这里Person不是class而是interface呢?


你谈到“当对象与对象之间的关系比较复杂的时候,ORM配置文件会很复杂”,但是我从来不配置,所以我不使用Hibernate这类R-O Mapping,我使用那种自动分析Object定义而自动创建/更新数据库的O-R Mapping。当我们在编程时,会不断修改class和interface定义,因此ORM必须能够自动分析OOPL代码产生的结构并自动维护数据库结构,例如当我们删除一个属性而增加了另一个,然后运行ORM,它仍然会给我们装入原先的数据(也就是说数据库的限制不能阻碍我们编程的脚步),而对新的属性给出默认值。

不过我下面还是把 Hibernate 也成为 ORM,这样可以代表整个一类系统。

关于“级联删除”,我相信如果我们不定义,ORM就不会帮我们删除。所以这不是问题。我们可以在Class或者Interface上使用触发器接口,然后ORM就调用触发器功能,而至于如何实现则完全由我们自己来写。这就比定义级联删除或者触发器存储过程要轻便许多。

同样地,虽然Hibernate定义了关联查询,以及LazyLoad,但是你也仍然可以不用它自动给你产生关联的对象属性(集),例如在对象的指向其它关联对象的属性中仅仅保存ID号但是不需要ORM给你装载那些ID号对应的对象,而用程序来(需要时才)显示查询出。这样也就不需要LazyLoad,因为你是靠自己的程序来决定什么时候才去加载关联对象的。

最后一个问题你说的“扁平”与“树状”的差别的意思,我猜你是说经常在一个对象的属性中直接设计为其它对象的集合,而不是它们的ID号的集合,这样就形成了树状;而关系树由于根本无需面向对象所以最多就只能用ID号之类的主键做外键。实际上这跟上面LazyLoad是同样的机制,你完全可以直接将关联的属性设计为ID号(或者ID号集合)。

换一种角度,例如我们首先开发了一个会员管理系统,之后又开发了一个会员奖品管理系统,那么我们正常情况下不应该去修改会员定义来包含奖品,而顶多只能在奖品中定义它自己属于哪个会员。这种大概也是你所说的“扁平与树状”的差别,但这恰好应该在面向对象设计时来解决,因为根源在于思想中我们对实际领域对象的理解和宣讲方式(自然的非软件专业的概念),而不是关系数据库表这种计算机软件专业的概念。

不过,说实在的,我最重视的还是我一开头所说的“支持多态、支持接口”的。正因为此,我所以不使用Hibernate,因为我觉得它只是R-O mapping,而不是O-R mapping。
ilovesijia 2010-03-16
  • 打赏
  • 举报
回复
回sp1234:

对于你说的会员系统以及会员奖品系统。就是说现有会员系统,后又会员奖品系统。

对于这种情况,我一般会这样做:
数据库层面:
一开始会有一个tb_Users会员表,后来会新增一个tb_UserPrizes会员奖品表。然后我会为tb_UserPrizes表中设计一个UserId的外键。而tb_Users表保持不变。

对象层面:
一开始会有一个User对象,后来会有一个UserPrize对象。同样地,我会为UserPrize对象设计一个UserId的属性,用来和User对象建立关联。

然后因为你可能现在想要查询一个会员列表,列表中每个会员需要包括他的所有奖品信息,此时,数据库层面我觉得很好解决,只要将tb_Users和tb_UserPrizes这两个表left join一下即可,也可以用一个视图来实现,比如叫vw_UserPrizes。但是对象层面该如何关联呢?我觉得最简单的办法就是建立第三个对象,比如叫UserWithPrize对象,该对象内部引用了一个User对象和一个UserPrize对象。这里,UserWithPrize对象相当与数据库中的vw_UserPrizes视图,完全对应起来。

所以,基于这种设计,我们就可以很简单的只要去查询vw_UserPrizes视图,并且把返回回来的列表放在一个
List<UserWithPrize>列表中即可。

我觉得这样的设计就是可扩展性强,当我要新增功能时,往往不需要修改或扩展现有的对象。而只需要新增对象并和原对象进行组合即可。在这个例子中,无论是数据库表还是对象,都是按照这种思路来实现。

我觉得这种设计可以让你的系统适应各种不断变化的需求。
monom 2010-03-16
  • 打赏
  • 举报
回复
又见sp1234 的回复,学习,学习。
bychgh 2010-03-16
  • 打赏
  • 举报
回复
来学习的~~~~~~~~`
ilovesijia 2010-03-16
  • 打赏
  • 举报
回复
回sp1234:

1.首先,我对你说的自动分析Object定义而自动创建/更新数据库的O-R Mapping表示非常佩服,如果真的能够做到不管对象之间的关系多么复杂,你都能自动创建Mapping,那确实非常好。

2.关于级联删除或级联查询等级联操作,确实Hibernate已经提供了开关以便让我们决定是否需要做级联操作。但我想表达以及强调的意思是,因为Hibernate会把对象之间的关系也映射到相关表的字段。比如下面这个例子,一个商品可能属于多个订单,而一个订单会包含多个商品,他们之间是属于多对多的关系。

public class Order
{
public virtual int OrderId { get; set; }
public virtual DateTime OrderDate { get; set; }
//多对多关系:Order有多个Products
public virtual IList<Product> Products { get; set; }
}
public class Product
{
public virtual int ProductId { get; set; }
public virtual string Name { get; set; }
public virtual float Cost { get; set; }
//多对多关系:Product属于多个Orders
public virtual IList<Order> Orders { get; set; }
}

ORM配置文件如下:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="DomainModel" namespace="DomainModel">

<class name="DomainModel.Entities.Order,DomainModel" table="`Order`" >

<id name="OrderId" column="OrderId" type="Int32" unsaved-value="0">
<generator class="native" />
</id>
<property name="OrderDate" column="OrderDate" type="DateTime" not-null="true" />
<!--多对一关系:Orders属于一个Customer-->
<many-to-one name="Customer" column="Customer" not-null="true"
class="DomainModel.Entities.Customer,DomainModel"
foreign-key="FK_CustomerOrders" />
<!--多对多关系:Order有多个Products-->
<bag name="Products" generic="true" table="OrderProduct">
<key column="`Order`" foreign-key="FK_OrderProducts"/>
<many-to-many column="Product"
class ="DomainModel.Entities.Product,DomainModel"
foreign-key="FK_ProductOrders"/>
</bag>
</class>
</hibernate-mapping>

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="DomainModel" namespace="DomainModel">
<class name="DomainModel.Entities.Product,DomainModel" table="Product">

<id name="ProductId" column ="ProductId" type="Int32" unsaved-value="0">
<generator class="native"/>
</id>
<property name="Name" column="Name" type="string" not-null="true" length="50"/>
<property name="Cost" column="Cost" type="float" not-null="true"/>
<!--多对多关系:Product属于多个Orders-->
<bag name="Orders" generic="true" table="OrderProduct">
<key column="Product" foreign-key="FK_ProductOrders"/>
<many-to-many column="`Order`"
class="DomainModel.Entities.Order,DomainModel"
foreign-key="FK_OrderProducts"/>
</bag>
</class>
</hibernate-mapping>


上面这个例子是多对多情况下在Hibernate中典型的对象设计和相关ORM配置。我觉得这个例子比较简单。我要强调的是如果当前对象之间的例子比较复杂,就像我帖子中所说的“查询论坛中某个版块下具有某种角色的所有用户”这个需求,其实对于数据库来说,应该是只要连接tb_SectionRoleUsers表和tb_Users表即可。
而如果用Hibernate来实现,该如何设计对象,并且如何来实现配置文件呢? 我觉得会非常复杂。

所以我帖子中最后一段表达的意思是,Hibernate等ORM框架为我们做了太多的事情,硬是完全把两种格式不一样的东西(对象和数据库表)建立映射关系,导致映射配置文件非常复杂。反而使我们不好维护整个系统。

另外,关于你说的在设计对象时,不需要去直接引用某个子对象或者是子对象的集合而只要保存一个子对象ID或者是子对象ID的一个列表的做法。我也表示很赞同,虽然这样做使得对象之间没有直接关系,应该说已经违背了OOAD的思想,而更类似于主表和从表的关系(从表会保存一个主表的ID)。所以这种情况下,所有的对象都仅仅只是一个数据包,没有直接关系。所有的关系都是需要我们自己动手去维护。而我赞同这种做法的原因是,这样给我们的感觉似乎更清晰,因为对象(数据)就是对象,而业务逻辑就是业务逻辑。对象和表之间的映射全部交给ORM映射框架完成,而所有对象之间的关系,就是所谓的业务逻辑,完全由我们自己来实现。我觉得这样的设计非常清晰,也很容易理解。如果按照Hibernate来做程序,我有时如果想知道某个子对象是怎么被删除或查询的,就还必须去看一下ORM配置文件是怎么配置的,以及看看相关代码是怎么写的,非常麻烦。而如果所有的业务逻辑都由我们自己来实现,那维护时就不必去多个地方检查是否设置了相关的关联了。只要去查看我们自己写的相关代码就能知道各个子对象是如何被查询或被删除的。

所以,相比之下,我更喜欢Entity+EntityManager+我们自己写的业务逻辑的方式来设计和实现一个应用程序。
其中
Entity是指:纯数据包,如果对其他对象有引用,则只包含其他对象的ID或ID的列表;
EntityManager是指:一个管理类,负责用来管理所有的Entity。为我们提供了操作对象的各种CRUD操作;
业务逻辑:完全由我们自己写,自己决定对象之间该如何级联操作,如级联删除或级联查询等;
sxmonsy 2010-03-16
  • 打赏
  • 举报
回复
这个还是得具体情况具体分析。
honkerhero 2010-03-16
  • 打赏
  • 举报
回复
自己封一个与数据库无关的面向接口的数据访问层,自己写DAL/BLL的一点点代码还是有时间的,轻量又好用,


Hiberinate都把人用傻了,好多用那个框架的人都说数据表必须有个自增列做主键,甚至还在网上起了大讨论。
啊兵 2010-03-16
  • 打赏
  • 举报
回复
并不是所有场合都适用啊!
周公 2010-03-16
  • 打赏
  • 举报
回复
我个人觉得ORM在一定的场合下可以使我们不用关注SQL语句的书写,把主要精力放在业务逻辑上,但是并不是适用于在所有场合。就像现在的机器人能代替人做很多危险的、机械的工作一样,但是不能代替人来思考复杂的问题。

62,041

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术交流专区
javascript云原生 企业社区
社区管理员
  • ASP.NET
  • .Net开发者社区
  • R小R
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

.NET 社区是一个围绕开源 .NET 的开放、热情、创新、包容的技术社区。社区致力于为广大 .NET 爱好者提供一个良好的知识共享、协同互助的 .NET 技术交流环境。我们尊重不同意见,支持健康理性的辩论和互动,反对歧视和攻击。

希望和大家一起共同营造一个活跃、友好的社区氛围。

试试用AI创作助手写篇文章吧