白皮书 & 分析报告
消除数据库层面的阻抗不匹配
Mary A. Finn
产品市场(拓展)经理
介绍
随着 Java 的成熟和广泛的接受,面向对象的程序设计成为应用程序开发的前景。因为它们有丰富的数据模型和支持提高生产力的概念,像封装、继承和多态,像 Java 、 C++ 、 COM 这样的对象技术成为今天的应用开发者的最爱。
然而,大多数的数据仍然存在于关系数据库中。数据库应用(任何存储数据的应用)的开发人员经常发现他们正面临一种阻抗不匹配的局面:在对象和关系模型之间继承关系断开了。努力使关系数据映射到要用到的对象格式经常对程序员的编程效率和应用的性能造成不利影响。
然而,可以通过正确的选择数据库减轻这种不匹配。本文定义了这种不匹配并给出了两个例子以显示它是如何影响应用的开发的。然后从正面和反面讨论三种数据库:关系数据库、面向对象的数据库和来自 InterSystems 的多维数据库—— Caché 。
理解阻抗不匹配
阻抗不匹配是一个来自电子工程领域的术语,但是在软件世界它用来指关系和对象数据模型固有的不同。
关系模型把数据组织成行和列。每行表现为一条记录,列表现为记录中的大量的数据项。如果数据过于复杂以至于不能用二维的表格描述,附加的表将被创建来保持“关系”信息。因此,在关系模型中的每张表都掌握一些但不是全部的数据,而真正的数据可能有非常多的记录和数据项。
对象模型不拘泥于在行和列中保存数据。开发人员创建一个定义—一个模板—完整地描述一个特定类的信息。每一条记录(对象)都是该类的特定的实例。因此每一个对象都包含了所有的数据项。但这并不是全部。类定义也可以包括一段代码,叫做方法。而关系模型里面没有这种概念。
一个简单的例子
为了举例说明两种数据模型间的区别,假定你正在开发一个帐目应用。你的应用将需要跟踪发票号,每张发票都有一些头信息(例如开票日期)、发票号和一行或多行项目。每行项目将包括:订购的产品信息和产品数量。
在关系数据库中建立发票模型的方法之一是创建两张表。一个叫做发票表( Invoice ),包括每张发票上只显示一次的头信息。另外一张表叫条目表( LineItems ),包含了 Invoice_Parent 、 Line_Item_Product_Code 、 Line_Item_Quantity 字段。第一个字段特别重要,因为它的值是关联本表到发票表的。
注意哪张表都不含有给定发票的所有信息。如果你的应用程序设计完了,例如,打印一张发票,它就必须访问者两张表来获得全部的信息。还要注意表中并不含有任何关于如何打印数据的指令。那些指令存在于数据库外面。
在对象模型中,数据不需要放入行和列,所以发票类定义将看起来像组成发票的所有的数据条目的列表。有包含在头信息中的属性,例如 InvoiceDate , InvoiceNumber 等等,和一个 LineItem 类的实例的集合。 LineItem 类包含了 ProductCode 和 LineItemQuantity 属性。
类定义只是数据格式的蓝图。每张独立的发票是发表类的一个特定的实例,并且包含特定的 LineItem 类的实例。因此,每个发票对象包含了给出的发票的全部信息,并且是只是该发票的信息。
但是类定义也许能够包含方法。例如,你的发票类也许包括一个 Print() 方法来治市如何格式化发票信息来打印出来。持久对象将包含一些方法, Save() 方法指定对象在数据库中是如何存储的。默认 Save() 方法的实现决定于数据库引擎的结构,由数据库厂商提供。
当操作数据库的时候阻抗不匹配
考虑到在你的帐目应用重创建一个有一行条目的新的发票的情况。如果你正在用关系数据库编程,你的代码将看起来像 Example #1 。它将包括两条插入语句,一个是向发表表添加头信息,另一个是向 LineItems 表添加条目。 Insert 是一个标准的 SQL 命令,关系数据库厂商将提共它。
Example #1 : 用关系型数据库建立一个新发票 If (flag="New") { Insert Into Invoice (Invoice_Date, Invoice_Number) Values(Today,:NewInvoiceNumber) Insert Into LineItems (Invoice_Parent,Line_Item_Quantity, Line_Item_Product Code) Values(:NewInvoiceNumber,:Quantity,:ProdOrdered) } |
使用对象模型的保存有一行条目的发票的代码在 Example #2 中。除了语法细节外,它看起来和关系的例子很想。主要的不同是 Save() 只调用了一次。
Example #2: 用面向对象的模型建立一个新发票 If (flag="New") { objInv=new Invoice() objInv.InvoiceDate=Today objInv.InvoiceNumber=NewInvoiceNumber objLI=new ObjInv.LineItem() objLI.LineItemQuantity=Quantity objLI.ProductCode=ProdOrdered objInv.Save() } |
现在假设你想在面向对象的语言中(例如 Java 或者 C++ )写业务逻辑,但是你需要在关系数据库中存储数据。为了完成这个, SQL Insert 语句必须写在你的发表类定义里面的 Save() 方法中。于是这儿就有一种阻抗不匹配的情况了—一个有集合的对象类必须被翻译成两个不同的关系数据库表。
设计中的阻抗不匹配
一种形式的阻抗不匹配可能突然出现在程序设计过程中。除了提供了更多的,更直接的建模方法外,对象技术带的一些概念还能够大大提高编程的生产力。特别是对象技术支持继承和多态的概念。
继承是指一个类定义可以得自另外一个类。例如,在你的帐目应用中,你可以创建一个 InvoiceTemplate 类,然后继承它的属性和方法创建 SoftwareInvoice 和 HardwareInvoice 类。随着应用的发展,如果改变了 InvoiceTemplate 类,继承自他的类将自动改变。
多态是指不同的方法可以实现共享一个通用的接口。例如,在 SoftwareInvoice 中的和 HardwareInvoice 中的 Print() 方法可以包含不同的格式的指令。然而,要打印一张发票的话,应用之需要载入一个对象到内存中,然后掉用它的 Print() 方法。应该感谢多态,对象将知道应该打印哪种格式。
继承和多态都不存在于关系模型中。一些大型的关系数据库,例如 Oracle 、 Microsoft SQL Server 和 IBM DB2 试图实现面向对象的概念,但是结果总达不到程序员的要求。
减轻阻抗不匹配的途径
给出的两个例子太简单了,但是他们很能够说明问题。工作需要“规格化”阻抗不匹配,并且随着应用复杂性的增长也急剧地加重。然而,阻抗不匹配的效果可以通过选择正确的数据库有效地削弱。让我们考虑三种数据存储的选择:关系数据库,“纯粹的”对象数据库和 Caché 多维数据库。
使用关系数据库
本文也讨论了如何使用一个关系数据库和基于对象技术的造成阻抗不匹配的应用程序。但是有时开发人员没有选择。他们也许需要操作保存在关系数据库中的数据。在这样的情况下,一种选择就是使用一种“对象关系映射”的工具,不论它是独立的工具还是一些所谓的“对象关系”数据库内带的功能。
本质上,映射工具创建了一个文件—一个映射—它包含了翻译对象和关系表的代码。开发人员必须精确地指定翻译是如何做的,那就是,哪一个对象属性对应哪一个表中的哪一个字段。一旦创建了映射,应用程序存取数据就必须每次都掉用这个映射。有些对象关系映射工具提供了运行时的缓冲组件来帮助弥补由于翻译关系和对象对性能造成的损失。
除了任何运行期的性能问题以外,对象 - 关系映射会大大降低应用开发进度。大多数映射工具没有实现或者只是部分地实现了对象模型的概念,例如继承,多态等。因此,当一个应用被修改时,就必须更新对象 - 关系映射。
开发人员为了解决这种不匹配也许想考虑把数据迁移到一个对对象更友好的数据库中。他们必须考虑迁移数据需要重新格式化数据以及使用对象 - 关系映射带来的性能损失。
使用对象数据库
初看起来,这种阻抗不匹配完全能够被把数据存储在纯对象数据库中解决。其实这个观点部分是正确的。通常地,面向对象的应用很容易和对象数据库交互。然而,有时这种阻抗不匹配发生在你想在数据库上运行一个 SQL 查询。 SQL 是世界上应用最广泛的查询语言,它是假定数据存储在关系表中的。有些对象数据库厂商通过对象查询语言( OQL )提供数据操作,但是这些语言没有被广泛接受。为了兼容通常的数据分析和报表应用,一个对象数据库必须支持 ODBC 和 JDBC ,并且必须提供把对象映射成为关系表的机制。
典型的解决方案是,再映射一次。映射的缺点—性能的损失和对数据模型发展的支持—仍然存在。但是这只是在数据库上运行 SQL 查询才会调用映射。
使用采用单一数据架构的多维的 Caché
这儿还有一个第三种选择来做数据存储—— Caché —— InterSystems 提供的多维数据库。尽管多维数据库在数据仓库领域经常被提到,但是 Caché 被设计为事务处理应用的一部分。只有它实现了减少阻抗不匹配,那就是采用单一数据架构。
我们要感谢单一数据架构,对象和关系数据模型共享 Caché 多维数据。多维数组很容易映射成表,因为表不过就是一个二维的数组。同样地,在对象和多维数组之间也很容易关联,因为多维数组不局限于关系技术的行和列的格式。在数据格式之间的翻译工作是自动的,它成为编译好的数据定义的一部分。对开发人员来说,每张表都等同于对象,每一个对象又可以看成一个或多个表。
Caché 单一数据架构的一些其它特性是:
完美的并发能力
通过关系结构更新的数据可以立刻通过对象结构访问,反之亦然。
支持数据模型更新
更改数据模型定义自动在对象和关系表现层体现。
完全支持 SQL
完全支持 SQL DDL , DML , 和 DCL 命令。
完全支持对象
对象模型的概念,像简单和多继承、多态、高级数据类型等都完全支持。
对象伺服
定义在单一数据架构中的对象能够提供 Java 、 C++ 或者 COM 对象,提供多种对象技术的兼容能力。
单一数据架构能够显著地改善阻抗不匹配,但是不能完全去除它。这儿还有一些概念,例如:对象方法和关系数据库触发器不能自动地共享。然而, Caché 仍然是开发人员绑定对象和关系数据访问的一个好的选择。
结论
近几年来, 面向对象的编程语言,像 Java 、 C++ 和 COM 成为应用开发的主导技术。因此,数据库应用开发者需要能够把数据映射成对象。
然而, SQL 是到目前为止用于数据分析和报表最主要的技术。因此,任何数据库必须能够具有把数据映射成为关系表的能力以使能够通过 ODBC 或者 JDBC 访问数据。
阻抗不匹配——对象和关系数据模型间固有的不同无法避免,但是它可以通过选择合适的数据库减少这种影响。对新的应用开发来说,把数据存储在多维的数据库,就像 Caché ,通过统一数据架构允许数据能够被映射成对象和表是很有意义的。
对于正在进行的应用开发,当现存的数据已经保存在一个关系数据库中时,开发者应该考虑把数据转换成多维的形式。这样他们将会避免性能上的麻烦和领他们头疼的对象关系映射的问题。

