介绍 当需要为软件系统系统提供一种可靠,灵活而又高效的对象持久化方法时,当今的设计师和架构师们面临着众多的选择。从技术的层面上,这个选择往往介于完全面 向对象,对象关系混合,完全关系化和建立在公开或专有文件格式上的常规解决方案之间(如:XML,OLE的结构化存储)。从提供者的层面 上,Oracle, IBM, Microsoft, POET 和其它的公司提供了相似,但是彼此间往往不相容的解决方案。 本文仅论述这些选择中的一种,即在完全关系数据库上层面向对象的类模型进行分层。这并不表明它是唯一、最好而又简单的解决方案,但是从实用的角度看,它是最常用的一种类型,却也是最容易被用错的一种。 我们先快速浏览两个设计领域的模型,并试图把它们连接起来:第一,介绍用UML表达面向对象的类模型;第二,关系数据库模型。 对每一个领域我们只涉及影响到我们任务的主要功能。然后我们将关注从类模型到数据库模型映射的技术和问题,包括对象持久性,对象行为,对象和对象标识之间 的关系。我们将总结对UML数据profile的回顾(Rational Software 推荐)。一些面向对象设计,UML和关系数据库建模的相似性也会被提及。 类模型是UML用来表达软件系统逻辑结构的主要工件。 它用来记录数据需求和模型领域内对象的行为。本文不讨论创建和详细描述该模型的技术,我们将假设已经存在一个设计好的类模型,它需要映射到关系数据库上。
类模型
行为
关系和特性
聚合是关联的一种形式,表示一个类多个对象的集合在另一个类中。复合是一种更强的聚合形式,说明一个对象实际上由其它对象构成。对于关联关系来说,它意味 着一个复杂的类属性,将该属性映射到关系模型时需要更详细的考虑。当一个类表示为生成许多对象实例的模板或模型时,对象需要在运行时有识别自己的方式,这 样被关联对象可以对正确的对象实例施加作用。在编程语言中,如C++,对象指针可能会传递,并使所指对象可以访问一个独一无二的对象实例。通常尽管一个对 象会被销毁,但是在需要时,又象上一次有效实例期间那样被重建。所以,这些对象需要一个存储机制来保留它们的内部状态和关联,并在需要时恢复所需状态。 继承给类模型提供一种方式,该方式提取通用行为到泛化的类中,使这个泛化类稍后可以做为在一般主题上诸多变异的原形。继承是一种管理重用和复杂性程度的方 式。如我们将看到的,关系模型并没有与继承关系的直接对应项,这给数据模型建立者建立一个从对象模型到关系框架造成了困难。从一个运行时的对象到另外一个 对象的导航是建立在完全引用的基础之上。一个对象有多种形式的连接(指针或唯一的对象标识),用这些连接可以定位和重建所需的对象。
关系模型 表和列:一个关系表是一个或多个列的集合,每个列在表结构中有一个唯一名称,并且被定义成一个特定基本数据类型,如:数字、文本、二元数据。表定义是一个 模板,表的“行”从这个模板中被创建,行可能做为一个表实例的实例。关系模型仅仅提供一个公共数据访问的模型。所有数据向外对任何一个过程开放,以便于被 更新,查询和操控。信息隐蔽(information hiding)是未知的。
行为
关系和识别
小节
对象模型中有丰富的关系集合:继承,聚合,关联,复合,依赖,以及其它。在关系模型中,可以仅使用外键来指明一种关系。我们已经对感兴趣的两个领域进行了介绍并比较了每一个领域的几个重要功能,然后将简单了解UML中关系数据模型的标注。
UML数据模型Profile(特性描述) 表和列 表在UML数据Profile中是带《Table》构造型的类,它在右上角显示一个表的符号。数据库中的列用《Table》类的属性来建模。
例如:上面图型显示与客户表关联的属性。在此示例中,对象ID被定义为表的主键,还有两个列:“Name”和“Address”。注意上图例子中列的数据类型是按照原DBMS的数据类型定义的。 行为 到目前为止,我们仅定义了表的逻辑(静态)结构。此外,我们将描述与列关联的行为,包括:索引,键,触发器,过程等等。行为表示为带构造型的操作。 下图显示我们讨论的表,它有一个主键约束和索引,均被定义为带构造型的操作。
注意:“OID”列上的PK标签定义了逻辑主键,而构造型操作“«PK» idx_customer00”定义了与主键实现相关联的约束和行为(即主键的行为)。 对上例进行增加,我们现在可以定义附加行为,如:触发器,约束,存储过程。见下图:
这个例子描述了下列行为:
使用上面提供的标注,我们可以在DBMS层次上,对复杂的数据结构和行为建模。另外,UML还提供表达逻辑实体间关系的标注。
关系
物理模型
一个组件表示了一个离散,可部署的实体。在物理模型中,组件可以映射到一个物理硬件(UML的节点)。对于数据库内的关系模式,我们用带《Schema》 构造型的包来表示。一个表可以放置到《Schema》中来建立它在数据库中的范围和位置。
从类模型到关系模型的映射
1. 类的建模
2. 标识持久对象
3. 假设每一个持久类映射到一个关系表
4. 选择一个继承策略.
对每一种方法,我们有对应的案例。但是我们推荐第三种方法,因为它最简单,最容易维护和最不容易出错。第一种方法提供了运行时最佳的性能。第二种方法是第 一种与第三种的折衷。第一种方法展开层次结构,在一个表里放置所有的属性,这样方便类层次结构中对类的更新和提取,但是不易于验证和维护。与“行”关联的 业务规则是难以实现的,因为表中的每一行都可能被实例化为层次结构中的对象。“列”之间的依赖关系可能变得相当复杂。此外,对层次结构中任何一个类的更新 将可能影响层次结构中其它每个类,如表中的列被添加,删除和修改。 第二种方法是一种折衷方案,提供了更好的封装和消除空列。可是,对父类的修改可能需要在所有子类的表中进行复制,更糟的是,有两个或多个子类的父类数据可 能被冗余地存储在许多表中;如果父类的属性被修改,那么要花相当的时间去查找相关的子表,并更新受影响的行。 第三种方法精确地反映了对象模型,它将层次结构中的每一个类映射成一个独立的表。父类或子类的更新是在正确空间的局部范围内进行的。对实体的任何修改被严 格限定于单个表内,因此表的维护也就相对简单。缺点是需要在运行时重构结构层次,来精确产生一个子类的状态。一个“Child”的对象可能需要一个 “Person”的成员变量用于表示它的父辈。由于这两者都需要加载,两次调用数据库来初始化一个对象。随着类结构层次加深,初始化或更新一个单独对象的 数据库调用次数也随之增加。 当你映射继承关系到关系模型时,理解上述要点是很重要的,这样你就选择最适合你的方案。
5. 为每一个类添加一个唯一的对象标识符 系统级别OID的一个例子可以是一个使用微软“guidgen”工具创建的GUID(全局唯一标识符),如{A1A68E8E-CD92-420b- BDA7-118F847B71EB}。类级别的OID可以用简单得数字实现(如:32位计数器)。如果一个对象具有对其它对象的引用,它可以采用使用它 们的OID的方法。那么,在运行时有效地将引用的对象从存储区加载到模型中。关于上述OID值的重点是它没有超出定义它为标识符的简单含义。它们仅是逻辑 指针而没有其它意义。在关系模型中,情况往往是不同的。 关系模型中标识正常地用一个主键实现。主键是一个表中的一组“列”,它们合起来可以唯一地标识一个行。例如:名称和地址可以唯一地标识一个“客户”。其它 实体,如:“销售员”引用“客户”,它们要实现基于“客户”主键的外键。这个方法存在的问题是:将业务信息(如客户名和地址)嵌入标识符中对我们目标的影 响。设想三个或四个表全部具有基于“客户”主键的外键,对于一个需要修改客户主键(如:增加一个用户类型)的系统修改,那么既要修改客户表,也要修改与外 键相关的实体,这个工作是十分巨大的。 另一方面,如果一个OID被当作主键实现,并为其它表建立外键,那么修改范围仅限于主表,并且修改的影响也因此大大减小。实际上,一个基于业务数据的主键 也许会被修改。如:一个客户可以修改地址和名字,既然这样,这个变化需要被正确的传递到所有其它相关的实体,更不用提改变部分主键信息的困难。 一个OID总是引用同一个实体,而不管其它信息怎么改变。在上面的例子中,客户可以修改名称和地址,与它相关联的表不需要被修改。当把对象模型映射到关系 表时,实现完全OID的标识符比采用业务联系的主键更加便捷。用OID做为主键和外键的方法将为对象提供更好的加载和更新效率,将维护服务减到最少。实际 上,一个与业务相联系的主键可以替换为:
再次重申,是使用有意义的键还是使用OID取决于被开发系统的实际需要。
6. 映射属性到“列” 对复杂属性(即属性为其它对象)使用下面详细的步骤来处理关联和聚合关系。
7. 映射关联到外键
8. 映射聚合和复合 弱聚合的第二个例子是:一个实体在那里使用或排除了另一个实体的所有权。例如:一个“人”的实体拥有一组股票,这标明这个人可能与一个“股票”表中的某些 股票有关联,也可能没有关系。但是每个股票可以联系一个人,或不与任何人发生关系。如果这个人不在了,这个股票将变为“无主”的,或者被传递给下一个人。 在这个关系模型中,可以通过每个股票有一个“所有者”列来实现,这个“所有者”列将存储一个人的标识符(OID)。 强聚合形式则有与之关联的完整约束。复合表明一个实体由部件组成,并且这些部件对整体有依赖关系。例如:一个人可以有很多证明文件,如护照,出生证明,驾 照等。这个人的实体可以由这样的一组文件组成。如果这个人从系统里被删除,那么证明文件也要被删除,因为这些文件被映射到一个唯一个体。 如果我们暂时忽略OID,弱聚合可能使用中间表来实现(对多对多的情况)或者在一个聚合的类或表采用一个外键来实现(一对多的情况)。在多对多关系的情况 下,如果父类被删除,中间表中的实体也要被删除。在一对多的情况下,如果父类被删除,外键输入(如“所有者”)必须被清除。 在复合的情况下,外键的使用是强制的,约束条件是父类删除后,部件也必须被删除。逻辑上,复合是有一种含义,部件主键形成了全部主键的一部分。例如:一个人的主键由他证明文件组成。尽管实际上是相当冗长的,但逻辑关系上为真。
9. 定义关系作用
10. 模型行为
不同数据库商的关系数据库通常包含不同形式的,基于SQL的可编程脚本语言,用来实现对数据的操作。最常用的例子 是触发器和存储过程。当我们混合对象和关系模型,要做的决定往往是:是实现类模型中所有的业务逻辑,还是将部分的业务逻辑放在关系DBMS中更常用而有效 率的触发器和存储过程中。从完全面向对象的角度看,答案是避免使用触发器和存储过程,并且将所有行为放到类中。这样会使行为局部化,从而提供了一个更清晰 的设计,简化了维护,并且提供了在不同DBMS提供商之间更好的移植性。 在现实世界,存储过程和触发器的设计目标底线可以是每秒至少执行几百次或几千次之多。如果为了追求模型的清晰,移植性,可维护性和灵活性,那么就将所有的行为放在对象方法中。
如果性能是关注的焦点,可以考虑将部分行为交给更有效的DBMS编程语言。注意到将对象模型与存储过程以安全方式集成所花的额外时间,包括远程影响和调试的问题,可能要比简单部署更高性能的硬件更多。 如前面所述,UML数据profile提供下列扩展(构造型操作),可用来对DBMS行为建模:
11. 产生物理模型
总结 |
|