ORM
0 前言
对象模型基于软件工程的一些原理,而关系数据模型则基于数学原理,特别是集合论的原理。两种不同的理论基础导致各自有不同的优缺点。对象模型侧重于从包含数据和行为的对象中构造应用程序,而关系数据模型则主要针对数据的存储。对象模型和关系模型之间的这种失配叫做阻抗不匹配。采用关系数据库的面向对象的软件项目应用都必定要涉及对象模型与关系数据模型之间的交互,由于面向对象的模型和关系数据模型的阻抗不匹配,当系统中的对象存储在关系数据库中时,就需要进行对象与关系的映射。研究人员提出了一些解决方案来进行对象模型向关系数据库模型的映射。
对于面向对象模型的数据存储方式,最直接的应该是使用面向对象数据库(OODB) 。然而目前市场上关系数据库占据着主导地位, 大量的应用系统采用关系数据库作为底层的数据存储和管理系统。用面向对象方法分析和设计应用、用关系数据库存储数据, 自然就涉及到面向对象模型与关系模型相互转换的问题, 即消除“阻抗不匹配”所带来的困难。下面详细谈谈对象模型与关系模型的差异及ORM的映射机制。
1 模型的差异性
对象模型相对于关系模型特有的模型属性有如下几个方面
Ø 对象的属性
对象是有状态的实体, 当使用关系数据库来保存对象的状态时, 通常会将对象的状态, 即对象的属性值存储于所对应的数据库表中。面向对象模型中对象的简单属性与关系数据库表的列之间可以简单地映射,如.Net 中的String 类型可以映射成Oracle中的VARCHAR 类型。但是如果对象的属性是复杂类型, 问题就会很复杂。因为面向对象模型可以方便地构造各种新数据类型, 而关系数据库的数据类型简单固定, 它只能存储处理简单的数据类型,如数字、字符串、日期等类型, 虽然可以通过在数据库中增加一种新的数据类型来解决这方面的问题, 但是当今数据库管理系统对用户自定义类型的支持并不完善,且不同数据库管理系统的用户自定义类型很难相互兼容。
Ø 对象的继承
因为关系数据库不支持继承的概念,而在面向对象语言中使用父类和子类来表达继承概念, 所以在将对象模型转换到关系模型时, 在数据库中表达其继承关系也是一个需要解决的问题。
Ø 对象间的关系
对象之间存在着复杂的关系, 这些关系包括关联、聚集和组成。在面向对象模型中可以很容易地建立对象之间的关系,但是在将对象之间的关系在关系数据库中表达时, 却要耗费大量的时间和精力,并带来了极大的复杂性。例如, 对象模型中简单的多对多关系在关系数据库中进行表达时可能就需要复杂的外键来实现。因此, 在将对象持久化到数据库中时, 对于对象间关系的持久化, 同样是一个需要解决的问题。
2 映射机制
对象模型通过以下机制映射到关系模型
Ø 属性映射成列
类属性通常映射成数据库中的零个或者多个列, 但是并不是每一个类属性都需要持久化。将属性映射成列时, 最常见的情况是将一个类属性映射到数据库表中的一个列。当类属性和表列的类型都是简单类型时, 比如日期类型或者字符串类型, 那么直接进行一对一映射即可。但是当需要映射的类属性的类型不是简单类型或者是关系数据库所不支持的类型时, 就不能通过这种映射方式来进行。例如, 关系数据库一般不支持.Net 中的Boolean 类型, 这时就需要将它映射成数据库中的字符类型或者数值类型。另外, 如果数据库字段的粒度比对象的属性更细, 通常就需要将对象的属性映射到多个表字段。在有的情况下也需要将多个属性映射成表中的单一列。
Ø 属性类型映射成域
关系数据库的数据类型有限, 远少于高级语言的数据类型, 难以表现高级语言中丰富的数据类型和自定义类型, 给数据接口程序的编写带来很大的麻烦。为解决这一矛盾, 在实现对象关系映射时, 需要在对象模型中的类型与关系模型中的类型之间进行转换。
Ø 映射继承到关系数据库中
在关系数据库中实现继承的策略取决于类到表的映射策略。类到表的映射,除了非常简单的应用外, 通常不会有类到表的一对一映射。将类之间的继承关系映射到关系数据库中有3 种基本策略。
( 1) 整个类层次一张表
该策略把一个完整类层次继承结构映射成一个数据库表, 而层次结构中的所有类的所有属性都存储在这张表中。采用这种策略的优点是: 性能好, 可以很容易的实现; 在数据库中提供了最佳的空间占用, 没有任何冗余属性。缺点是: 对象与数据库之间的耦合程度比较高, 在类层次中增加或者删除一个属性时, 就必须对数据库表做相应改动。
( 2) 每个具体的类一张表
在该策略中, 每一个具体的类映射到一张表, 并将该类的父类的属性加入到同一张表中, 抽象类不需要映射到表。其优点是: 一次数据库操作就可以完成一个对象的读取。该策略的缺点是: 类与数据库的耦合程度比较高, 增加或删除父类的属性会导致对所有子类所映射的表的结构的修改; 对多态和继承的支持不够好; 数据库中的冗余字段比较多。
( 3) 每个类一张表
在该映射方式中, 不管是具体类还是抽象类, 每个类都将映射到一张表。采用这种策略, 必须在存放父类实例的表和存放子类实例的表之间建立具有唯一性的外键约束关系, 在每个表中插入一个识别ID 将子类所在表的记录和父类所在表的记录关联起来。这种策略的优点是: 类与数据库之间的耦合程度比较低, 类属性的变动引起的表结构的变动很小; 在关系数据库中最大程度的实现了对多态的支持;空间节约性好, 唯一的冗余就是增加了额外的识别ID 来连接不同的继承层次; 映射比较直接, 容易理解, 维护成本低。该策略的主要缺点是: 性能比较差, 访问类的实例时, 需要跨表关联; 可能导致频繁访问父类所对应的表。
Ø 映射关系到关系数据库中
数据库中不应该只保存持久对象, 还应该保存持久对象之间的关系。对象之间的关系除了继承, 还有关联、聚集和组成,它们之间的区别在于对象之间的绑定程度。对象间的关系可以分为一对一关系、一对多关系、多对多关系, 对于不同类型的关系, 采用的映射策略不同。要实现一对一和一对多关系, 只需要将一张表的键包含在另一张表中即可。要实现多对多的关系, 需要关联表的概念, 它是一种数据实体, 唯一的目标就是在关系数据库中维护两个或者多个表的关联。
( 1) 一对一关系
这种关系可以通过外键方式实现, 在任意一个类所对应的表中加入另一方所对应的表的主键作为外键。
( 2) 一对多关系
同样是通过外键方式来实现, 在“多”的一方所对应的表中加入“一”的一方所对应的表的主键作为外键。
( 3) 多对多关系
在多对多关系的映射中, 需要额外创建一张关联表, 在关联表中存放两个关联的表的主键作为属性, 而关联表的主键一般是这两个表的主键的组合。
3 参考模型
ORM 系统的参考模型附图所示, 在该系统中有一个映射信息库, 其中存放了对象与数据库表的映射信息: 当业务领域类需要持久化时, 持久层就会参照映射信息库, 从数据库中读出该类对应的表, 执行相应的SQL 代码, 如果是查询操作, 那么将参照映射信息库把查询结果集转换成对象。业务领域类与持久层之间传递对象, 持久层向关系数据库传递SQL 语句,关系数据库向持久层传递数据记录。这样业务领域类在存取对象时, 如同在对象数据库中存取一样。
ORM 系统的参考模型
这种方法的好处是应用程序开发者不需要了解数据库模式, 事实上, 他们甚至不需要知道对象是保存在关系数据库中。另一方面, 当数据库模式发生改变时,它并不影响面向对象代码, 因为持久层会自动跟踪这种改动( 通过修改映射信息库中的对应关系) 。缺点是对应用程序有些性能上的影响, 但可以通过采取如缓存、延迟加载等相应技术减少这方面的影响。
4 总结
对象持久化技术在帮助开发者大幅度降低与持久化相关的编码工作量的同时, 将数据访问逻辑从业务逻辑中分离出来,使得应用系统的结构更加清晰并且具有了更好的扩展性,极大地促进了目前基于关系数据库的应用系统的开发。