关系持久性建模

关系持久性建模
 
面向对象是伟大的技术。关系数据库是伟大的技术。如果这两种技术相互隔绝,那将是人类生活的一次灾难,更是我们的巨大悲哀。幸运的是,这种假设没有成为现实。上述两种伟大的技术得到了人们的重视,同样应该受到重视的还有如何将它们调和在一起。
 
并不是世界上所有的东西都能够轻而易举地结合起来。关系数据库和面向对象就是这样。可是造物弄人,偏偏是这样背景不同特性迥异的两个东西却有着一个无法抗拒的理由把它们联系到一块。同时使用关系数据库和面向对象技术可以使我们编写出能够管理大量数据的复杂的应用程序。正是基于此,才使我们有如此大的冲动,想要认真探究一下关系数据库与面向对象之间究竟有哪些异同。
 
首先让我们来了解一下关系数据库系统所具有地特点∶
    数据以二维表格形式表达;
    表中数据之间的关系以存储在表中的值来表达;
    使用SQL,可以动态地创建数据之间的关系;
    具体的某些数据实体之间的关系在数据库最初设计时,不一定要定义或可观察。
在来看一下面向对象系统地特点∶
    应用程序由一组对象组成,对象当然包括属性和操作;
    对象是由开发者定义的数据类型;
    对象之间可能具有很复杂的关系,如继承、关联、聚合和组合;
    类,准确地说是数据类型,之间的关系不容易修改,也不容易在系统建成之后动态创建;
    持久对象应该可以存储到外存储器上。
基于他们各自的这些特点,关系数据库和面向对象之间存在以下不同∶
    类型系统不同;
    语言不同;
    范例不同;
    基本数据实体不同。
 
不同的类型系统
关系数据库类型系统比较简单。关系数据库数据类型用来定义数据库表中的字段。存储在字段里的数据必须符合该列的类型。每一行每一列的数据类型必须是原子的。对象模型则比较复杂。每个对象都可以包含众多属性和操作。所以对象显然不是原子性的。我们根本无法在表中存储对象。对象的特性是封装、继承和多态性。而关系数据库里谈这些都是废话。
 
不同的语言
关系数据库里我们使用SQL完成所有的事情。但是SQL没有处理对象的能力,不可能支持封装、继承、多态。SQL最基本的目的就是存取数据库表里面的数据,这是它唯一能做的。而面向对象的语言则可爱得多。支持封装、继承、多态。我们可以随心所欲创建复杂的数据类型。但是当我们必须把大量持久对象写在硬盘上并且时不时进行查询时就会发现,这些语言在这方面的能力实在蹩脚。SQL可以存储并查询大量数据,但没有对象处理能力。面向对象语言能够处理复杂的对象,但对象持久化方面的能力非常弱。
 
不同的范例
使用关系模型必须让数据适应模型,而面向对象则需要尽力使模型适应真实世界。
 
不同的基本数据实体
对象模型到关系模型的映射,是类到关系表之间的映射。类对应着表,对象对应着记录,属性对应字段。困难在于,面向对象语言的基本数据实体是类,而关系模型的基本数据实体是字段。
 
以上的四点不同最终导致了我们在设计阶段的困难。对于我们这些笃信面向对象的家伙来说,灾难往往发生在设计阶段接近尾声,编码工作即将来临之际。当我们费尽九牛二虎之力,在用况图上画出一堆又一堆达芬其的鸡蛋,完成了充满律动的顺序图,制作了严肃规整的类图并且在上面布满各式各样的小箭头之后,突然发现,我们真正需要的是一个能够方便存储大量数据的关系数据库,借助它才是把那些活跃的小对象们安顿到硬盘上的最好方式。但是我们如何才能得到这个急需的关系数据库呢?难道我们就应该回到最初的需求文档,按照传统的方式,从头再来一遍吗?当然不是。这并非是单纯的怕麻烦,更重要的是,我们需要进行持久化的是对象,而不是传统意义上的“数据”,毕竟我们要打造的系统是一个面向对象的系统。按照传统方法绘制E-R图未必能够设计出真正符合系统需要的数据库。既然如此,我们就完全有理由按照面向对象的思路继续走下去。此时的我们真正需要做的是绘制一个持久模型,借助它我们完全可以把对象映射到关系数据库。
 
要把对象映射到关系数据库其实很简单,我们只需要一些基本的技巧,简单的说,有如下几项∶
    把属性映射到列;
    把类映射到表;
    在关系数据库中“实现继承”;
    映射关联、聚合和组合。
 
把属性映射到列
类的一个属性可以映射到一列,也可能需要映射到多个列,当然也可能需要把多个属性映射到表的一列。但是通常都只需要把一个属性映射到一个列。另外,并非所有的属性都需要持久化。比如,有一些属性完全可以当成计算列来处理。
 
把类映射到表
把类映射到表。这句话看起来比较容易理解,但是却容易让人产生误解。有些人可能会误以为一个类就应该映射到一张表。但是这并不总是正确的。实际上这种一一对应并不是总那么灵验。有些时候一个类也需要映射成多个表,而另一些时候多个类却也可以映射成一张表。这需要一些经验,需要具体问题具体分析。
 
在关系数据库中“实现继承”
这当然是不可能的。前面已经反复提到过,关系数据库不可能支持继承。这个问题的本质是,当把类映射到表的时候,如何解决那些存在泛化关系的类。我们有三个方法应对这种问题,都不复杂。三种方法各有忧略,没有哪一种是十全十美的。我们不妨通过实例来理解这些方法。
 
首先,我们看到这样一张类图:
 
图1:三个要映射到关系模型的类
 
人类显然是一个抽象类,类名是斜体字。它有两个子类,学生和教授。学生类只有一个属性,成绩。同样教授类也只有一个属性,工资。我们忽略掉方法,因为和我们要讨论的问题无关。我们要把这样一个类层次映射到关系数据库有三种方法可以选择。
 
方法一:对整个类层次使用一个数据实体
说得明白点就是把三个类影射到同一张数据表里面去,三个类的所有属性都映射成同一张表里的列。就像图二这样。
 
图2:对整个类层次使用同一个数据实体(这不是类图,尽管看起来很像)
 
 
这个方法很简单。这个表用起来也很方便。但是,我不说大家也知道。简单的东西往往都有一些小缺憾。浪费空间是显而易见的,即使我们只有三个类。但是设想一下如果我们有五个十个甚至更多的类,它们之间存在继承关系,的时候,这样作会怎么样。那样对空间的浪费真的是可怕的。另外,如果某一个类增加了一个属性,那么表就要增加一个属性,这样实际上影响了三个类的对象。如果增加单个属性时发生了错误,会影响整个层次中的所有类。这种错误一旦在程序运行过程中发作,你可能根本摸不到头脑,它们真的非常隐蔽,令人难以察觉。
 
方法二:每个具体类都使用一个数据体
每个具体类都会被映射到一张表。所谓具体类,在本例中,是指“学生”和“教授”类,当然不包括抽象类“人”。每个表代表着一个类,它包含那个类的所有必须的属性,更重要的是它包含了那个类的继承属性。如图3所示。
 
图3:每个具体类都使用一个数据体(这不是类图,尽管看起来很像)
 
 
这种方法同样有一个显著的缺点,如果需要修改一个类,那就必须修改它的表和所有子表。另外,如果有一个人即是学生又是教授的时候就比较麻烦了。这样需要在两个表里面分别存储同一个人的记录。而且这个人会有一个学生编号和一个教授编号。有一些时候,可能这是无法容忍的。
 
方法三:每个类使用一个数据实体
为每一个类创建一张表。子类的表的主键同时作为外键。如下图:
 
图4:每个类使用一个数据实体(这不是类图,尽管看起来很像)
 
我最喜欢这种方式,因为这样最符合面向对象的概念。某一个类发生变化时,也很容易修改相应的表。但是,缺点是数据库里面会有很多表。读写数据的效率可能会有一些慢。另外,采用这种方法还要注意灵活使用视图。正如前面所说的那样,没有任何一个方法是十全十美的。究竟选择哪种方法,需要在实践过程中具体问题具体分析,择其善者而从之。
 
映射关联、聚合和组合
仅仅把类映射到表,属性映射到列,这样显然是不够的。与此同时还必须把类之间的关系映射到关系数据库里面。类之间的关系无非就是继承、关联、聚合、组合。继承刚才已经单独讨论,现在就来看关联、聚合和组合是如何映射到关系模型中去的。
 
使用外键在关系数据库实现类之间关联
可以使用外键在关系数据库中实现关联。比如,存在这样一种关联关系,“一个雇员工作在一个岗位上”。类图显然应该如下图所示:
 
 
图5:一个雇员工作在一个岗位上
 
在关系数据库里,可以在雇员表上添加一个外键,像下图这样。
 
图6:在雇员表上添加一个外键(不是类图,尽管看起来很像)
 
通过关联表在关系数据库实现类之间的多对多关联
假设存在这样一种常见的关联,“一个骑士可以骑多匹马,每匹马也可以由多名骑士来骑”。类图如下:
图7:一个骑士可以骑多匹马,每匹马也可以由多名骑士来骑
 
在关系数据库上加一个关联表来表示两个类之间多对多的关联关系。
图8:使用关联表表示类之间多对多关联关系
 
使用以上介绍的技巧,完全可以把持久对象有效映射到关系数据库里面。根据我自己的尝试,这的确是一个有效的方法。如果大家感兴趣不妨在实践中尝试一下。
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值