ORM(Object/Relation Mapping)解决的主要问题是对象-关系的映射。域模型和关系模型分别建立在概念模型的基础上。域模型是面向对象的,而关系模型是面向关系的,一般情况下,一个持久化类和一个表对应,类的每个实例对应表中的一条记录。
Hibernate是目前最流行的开源ORM框架
ORM中的映射关系:
ORM的主要目的是通过类和对象操作数据库,所以在ORM中必须解决编程语言中的类与对象和数据库中的表之间的映射。
(1)类与数据库中表的映射:数据库中的每一张表对应编程语言中的一个类,当用户对类进行基本操作(如创建实现,修改对象的属性,删除一个实例)时,ORM框架会自动对数据库中的表进行相应的CURD操作。
(2)对象与表中记录的映射:关系数据库中的一张表可能有多条记录,每条记录对应类的一个实例,当用户对一个对象进行修改时,ORM框架会自动对数据表中的相应记录进行修改。
(3)类的属性与数据库中表的字段的映射:数据库中表的字段的数据类型与类中的属性的类型也是一一对应的。
—————————————————————以下为转载内容——————————————----————
对象-关系数据库之间的映射
Scott W. Ambler 著,Adams Wang 译
面向对象设计的机制与关系模型的不同,造成了面向对象设计与关系数据库设计之间的不匹配。
面向对象设计基于如耦合、聚合、封装等理论,而关系模型基于数学原理。不同的理论基础导致了不同的优缺点。对象模型侧重于使用包含数据和行为的对象来构建应用程序;关系模型则主要针对于数据的存储。当为访问数据寻找一种合适的方法时,这种不匹配就成为了主要矛盾:使用对象模型,常常通过对象之间的关系来进行访问;而根据关系理论,则通过表的连接、行列的复制来实施数据的存取。这种基本的不同使两种机制的结合并不理想。换言之,需要一种映射方法来解决该矛盾,从而获得成功的设计。
1.映射对象
.属性类型映射成域
.属性映射成列
.类映射成表
。在关系数据库中实现继承
.映射关系
。1对1
。1对多
。多对多
。关联与依赖
。相同的类/表,不同的关系。
1.1 属性类型映射成域
UML中的属性类型(Attribute Type)映射成数据库中的域(Domain)。
域的使用提高了设计的一致性,而且优化了应用的移植性。简单的域是非常容易实现的,仅仅需要替换相对应的数据类型和数据的尺寸。同时,对于使用域的属性,可能要求为域的约束加入SQL的Check串。例如,限定域的取值范围等。
枚举域(Enumeration Domain)限定了域允许取值的集合。枚举域比简单域的实现更复杂。下表对各种实现方法进行了比较。
实现方法 |
优点 |
缺点 |
推荐 |
Enumeration String:定义SQL约束来限定取值 |
简单 |
不适于数量较大的集合
|
常用的实现方法 |
为每个枚举值定义标志:为枚举取值定义布尔字段 |
解决了命名的问题 |
冗长——每个取值均需要一个布尔字段 |
在枚举值之间是并发关系,可同时出现时 |
枚举表:在表中存储枚举值的定义。可采用为每个枚举定义表或共用单个表的方法 |
有效地控制了数量大的枚举值。可在不改变应用代码的前提下,扩充枚举值 |
对于偶尔发生的应用,显得笨重。必须为枚举值的读取定义代码 |
适合于大数量的枚举值和开放式枚举值的情况 |
对枚举值进行编码:将枚举值编码为通常的数字 |
保存了磁盘空间,易于在多种语言中处理 |
复杂化了维护和调试 |
仅在处理多语言应用下使用 |
1.2 属性映射成字段
类的属性映射至关系数据库中0个或多个字段。
并不是类中的所有属性都是永久的。例如,发票(Invoice)中的合计(grandTotal)属性可能只是用于计算而不需保存在数据库中。另外,有时某个对象包含其它对象,如顾客(Customer)中的Address属性(Address本身映射为数据库表)。此时,属性映射成多个字段。
1.3 类映射成表
类直接或间接映射成表。
除非是非常简单的应用,类与表之间才会存在一一对应的关系。本节列举三种策略在关系数据库中实现继承。
1.3.1 在关系数据库中实现继承
在关系数据库中,一个关键问题是表主键的唯一性策略的选取。适当的方案能优化继承、组合及对象之间关系的实现。进一步的讨论参见对象标识符(OID)。
考虑类的继承问题。在关系数据库中保存对象时,基本上问题归结于“如何在数据库中组织被继承的属性?”该问题的不同解决方案会影响到整个设计。
关系数据库中实现继承的方法可划分为三类:
1. 将整个类层次映射为单个数据库表。
类层次的所有类映射为单个的数据库表,表中保存所有类(基类、子类)的属性。
优点:
实现简单。
支持多态——对象角色发生变化,或存在多重角色时。
报表操作实现简单:表中包含了所有信息。
缺点:
增加类层次中的耦合。类层次中任何类的属性的增加都会导致表的变更;某个子类属性的修改会影响到整个层次结构,而不仅仅是该子类。
浪费了大量的数据库空间。
可能需要指明具体的角色。
2. 每个具体子类映射成单个数据库表。
数据库表包括自身的属性和继承的属性,每个具体的子类包含各自的OID。抽象的基类不参与映射。
优点:
报表操作实现简单:表中包含了具体子类的所有信息。
缺点:
类的修改会导致相对应的表及其子类所对应表的更改。
角色的更改会造成ID的重新赋值(因为不同子类的ID可能重复)。
难以在支持多重角色时,保持数据的完整性。
3. 每个类均映射为数据库表。
为每一个类创建数据库表,表中包含特定于该类的属性和OID。
优点:
与面向对象的概念的一致性最好。对多态的支持最好,对于对象所可能充当的角色仅需要在相应的表中保存记录。
易于修改基类和增加新的类。
缺点:
数据库中存在大量的表。
访问数据的时间较长。
对报表的支持较差,除非定义视图。
以上三种方法各有优缺点,没有一种是完美的。下表为它们提供对比。
考虑因素 |
类层次——表 |
具体子类——表 |
类——表 |
报表 |
容易 |
中等 |
中等/困难 |
实现 |
容易 |
中等 |
困难 |
数据访问 |
容易 |
容易 |
中等/简单 |
耦合 |
非常高 |
高 |
地 |
访问速度 |
快 |
快 |
中等/快 |
对多态的支持 |
中等 |
低 |
高 |
1.4 关系映射
不仅仅是对象需要被映射至数据库,对象之间的关系也需要映射至数据库。对象之间的关系可分为:继承(Inheritance),关联(association),聚集(aggregation),组合(composition)。要有效地映射关系,必须理解它们之间的不同点,如何实现一般的关系以及如何实现特定的多对多关系。
1.4.1 关联与聚集/组合之间的区别
从数据库的角度,关联与聚集/组合之间的区别在于对象间的耦合程度。对于聚集/组合,在数据库中对整体进行操作时通常需要同时对部分进行操作,而关联则不然。
1.4.2 关系数据库中实现关系
关系数据库中通过使用外键来实现关系。外键允许将表中的某一行与其它表中的行相关联。实现一对一或一对多关系,仅仅需要在表中加入另一个表的主键。
· 可选的1对强制的1
将外键放置在可选的一端,该外键不能为空值。例如,某公司员工使用电脑的情况,要求员工最多能使用一台电脑,且电脑资源应充分的利用。
· 其它1对1的情况
外键可放置在任意一边,具体情况依赖于性能等因素。注:对于1对1的情况,不要在两个表中均放置对方的主键。这样,增加了冗余,而且并不会提高性能。对于强制性,一般在业务规则的对应层实现,不在Persistent Layer中实现。
· 1对多的情况
将外键放置在“多”的一方。外键的空/非空由对1的强制性决定。
· 多对多的情况
实现多对多关系,需要引入关联表(associative table)的概念。(参见术语)
传统实现中,关联表的属性包含关系中两个表的主键,并且关联表的主键往往是它们的组合。另一种实现方法是将关联表视为普通表,使用自身的主键OID,然后加入实现关系所必需的外键,
该方法的好处在于Persistent Layer中,所有的表具有相同的形式,简化了实现;另外,提高了运行时效率:一些数据库在连接具有复合外键的表时,性能较差;并且,存在着向Accesses表中增添字段的可能,如:需要增加对帐户访问安全性检查,可能某个帐户只允许取钱,而另一个帐户拥有所有的权限。
2. 引用完整性与关系约束检查
在UML中,对象之间的关系通常指关联、聚集、组合。其中,聚集是更加严格意义上的关联;而组合则是更加严格的聚集。对象之间的关系反映了具体的业务规则,因此将对象映射到关系数据库时,必须保证对象之间的关系——定义并确保数据库上数据的约束。
在采用了对象标识符(OID)中OID的策略时(即所有的对象具有唯一的ID,相应的数据库中所有表的主键均具有唯一性;OID不具有业务内涵),则在数据库级发生更新时,不会出现完整性问题,但就对象交互及满足业务规则而言,对约束的普遍讨论具有一定的意义。以下就1对多的情况考虑限制检查。(1对1关系可以视为特殊的1对多关系;多对多关系则可以分解为两个1对多关系)
2.1 对象之间的关系和父表操作的约束
· 关联
关联是一种较弱的关系。体现了实体间的联系。关系较松散,可能不需要映射,具体表现为不需要保存对方的引用,而仅仅在方法上有交互;如果在数据上有耦合关系,可能需要映射。
父表操作的约束:
父表的Insert操作,对M-O约束,父表中间的记录可以没有任何约束地添加到表中,因为这种约束中父亲不一定必须有子女。
父表的键值修改操作,只有在子表中其所有子女的对应值均做修改后,才能修改。即一般采用级联更新的方法。
即:
1. 插入新的父记录,将子表中原对应记录外键更新,删除原父记录。对该操作进行封装。
2. 采用数据库提供的级联更新的方法。
父表的删除,父亲只有在其所有子女均被删除或重新分配之后该父亲才能被删除。在Professor-Student关系中,所有学生可以重新分配。
对应的实现:
1. 先删除子记录,再删除父记录。
2. 先行将子记录的外键更改,再删除父记录。
3. 采用数据库提供的级联删除操作。
在Project与Employee的关联中,存在O-O约束(Optional-Optional)。
父表操作的约束:
理论上,在具有这种关系的约束中,任何类型的修改都没有限制。Project和Employee中的行可以按需要而进行修改。具体的处理方法与聚集相同。
· 聚集
关联的一种特殊形式,指定了整体(Whole、Aggregate)与部分(Part、Component)之间的关系。
父表操作的约束:
父表的Insert操作,对O-M约束,一个父亲只有在至少一个子女同时被加入,或至少已经存在一个合法的子女时,才能被加入。例如,图16中的Club和Student之间的关系,一个新的Club只有在已经有学生时才能被加入(或者该学生已经存在或者可以创建一个学生,或者修改一个学生所在俱乐部的值),总之必须要已经有适当的学生存在。
具体的操作:
1. 可以先行加入主表记录,再修改子表的外键。
2. 同时加入主表、子表记录(次序无关),再更新子表的外键。注:如果先加入子表记录时,可能在关系数据库中无法保存加入子记录的数据集。其业务规则更倾向于已有子对象的组合。
父表的键值修改操作,只有当一个子女被创建或已经有一名子女存在才行。也就是说只有当至少一名滑雪者愿意参加Scuba俱乐部或者已经有一名学生参加Scuba俱乐部时,SKI Club才可以改名为Scuba俱乐部。
针对于实现而言:
1. 在修改的同时将子表外键置空。
2. 子表需要级联修改。
父表的删除,理论上删除父亲是没有限制的。实际上,删除主表记录时,不采用级联删除子表的方案,而采用将子表的外键置空。该方法与聚集的语义是相一致的。
· 组合
组合指具有强主从关系和一致性的一种聚集关系。在合成对象创建之后,可能创建多个成员。成员一旦创建,就与合成对象具有相同的生命周期。成员可以在合成对象终止之前显式的删除。组合可以是递归的。如旅馆帐单与帐单上条目的关系。
父表操作的约束:
父表的Insert操作,可能随后需要生成子女,即在子表中创建新的行。也可能通过对子表的重新分配来实施完整性限制,如插入InvoiceNumber = 2的记录,接着将DailyCharge表中原外键为5的修改至2。即将Insert操作封装为原子操作。但根据组合的语义,上述情况更适合使用聚集来描述。
父表的键值修改操作,只有在子表对应外键的值修改成新值时才能执行。根据组合的定义,更有可能是先创建新的父表记录,接着修改子表所有原对应的记录,使其与父表的新记录关联,最后删除原父表记录。
父表的删除,只有在子表中所有相关的行全部删除或重新分配之后,才能删除父表中的记录。同样,根据组合的定义,一般对子表进行删除操作。
父表上操作小结:
对象关系 |
关系类型 |
Insert |
Update |
Delete |
关联 |
数据无耦合关系则一般不映射 |
|||
O-O |
无限制 |
无限制,对子表中的外键可能需要附加的处理 |
无限制,一般将子女的外键置空 |
|
M-O |
无限制 |
修改所有子女(如果存在的话)相匹配的键值。 |
删除所有子女或对所有的子女进行重新分配。 |
|
聚集 |
O-M |
插入新的子女或合适的子女已存在 |
至少修改一个子女的键值或合适的子女已存在 |
无限制,一般将子女的外键置空 |
组合 |
M-M |
对插入进行封装,插入父记录的同时至少能生成一个子女 |
修改所有子女相匹配的键值 |
删除所有子女或对所有子女进行重新分配 |
2.2 字表操作的约束
施加子表的约束主要是为了防止碎片的产生。一个明显的区别是,在一些情况下,一个子女(子表中的记录)只有在当其兄弟存在时才能被删除或修改。如在O-M、M-M约束中,即最后一个存在的子女是不能被删除或修改的。此时,可以对父记录进行即时的更新。或者禁止该操作。而子表约束的实现,可以通过在数据库中加入触发器;更合理、可行的方法是将子表一方的限制,在业务层中实现。
子表上操作小结:
对象关系 |
关系类型 |
Insert |
Update |
Delete |
关联 |
数据无耦合关系则一般不映射 |
|||
O-O |
无限制 |
无限制 |
无限制 |
|
M-O |
父亲存在或者创建一个父亲 |
具有新值的父亲存在或创建父亲 |
无限制 |
|
聚集 |
O-M |
无限制 |
兄弟存在 |
兄弟存在 |
组合 |
M-M |
父亲存在或者创建一个父亲 |
具有新值的父亲存在(或创建父亲)并且兄弟存在 |
兄弟存在 |
以上的讨论是从关系数据库的角度出发,但在对象的处理中仍具有意义。在对对象进行增、删、改操作时,与之对应的数据库记录操作应同它相对应。
关系数据库基于关系数学、函数依赖等特性,无法充分体现对象之间的关系和限制。所以,即使使用数据库来强制各种约束,也不一定能保证满足业务规则的需要。其次,数据库的某些约束与生产厂商和版本相关,不利于进行移植。
2.3 小结
ID的选取:采用对象标识符(OID)中的OID策略。由于对象标识OID同时作为表的主键,并且不具有业务意义。因此,可以避免在数据库操作时的很多限制,而相应的约束的处理由Persistent Layer转移到业务层来实现。
数据库表的映射:采用子表外键为空的方案映射对象。
父表的Insert操作:如果子记录已经存在,则先插入父记录,再更改子表的外键,如将空值更新为父记录,或者更改外键——此时,可能需要在业务层对子对象(子表)的完整性进行判断。如果子记录不存在,则先插入父记录,然后根据父记录的主键直接构造子记录。
父表的Update操作:如果更新的父记录不存在,则先插入父记录,更新子表,删除原有的父记录。反之,直接修改子表记录,删除原有的父记录。
父表的Delete操作:先删除子表记录或将其外键更改(置空或修改),删除父记录。
子表的操作:子表的操作在业务层中实现。
尽量避免使用存储过程或触发器。
应用程序在执行数据约束时有很重要的作用。存在四类约束:M-M、M-O、O-M、O-O。键值的修改可能会改变表之间的关系,而且可能违反一些约束。违反约束的操作是不允许的。前面的规则只是为具体实现提供了可能性。具体的应用必须根据实际的要求和业务规则进行适当的选择。但在设计和开发时,必须考虑所分析的约束。
3. 对象标识符(Object ID)
针对于对象,需要能够唯一识别它们的标识符。在关系数据库中,对应的概念称之为键(Key);在面向对象的技术中,称之为OID(Object ID)。OID在对象模型中的典型实现是作为完整的对象,而在关系模型中,则作为整数来实现,或者对于较大的应用中,以若干整数来实现。
OID用来在关系数据库中唯一的标识对象。
图18描述了OID类的可能实现,图19则显示了映射机制。
OID简化了关系数据库的主键方案。尽管OID并未完全解决对象间的浏览问题,但是它确实简化了操作。假设你不愿使用遍历的方法来读取聚集对象的成员,例如:发票与发票中的条目,则仍可以使用表关联来实现,至少提供了实现的可能。
使用OID的另一个好处是使开发时易于维护对象间的关系。当所有表的主键采用相同类型的列来实现时,非常容易编写使用该特性的通用代码。
3.1 OID不具有业务意义
OID在任何情况下,都不应包含业务内涵。存在业务意义的任何列都有潜在变化的可能。而在关系数据库中,采用有意义的主键是致命的错误。如果用户决定改变字段的业务含义,则需要在所有使用到该信息的地方进行修改。主键的作用应是保持唯一性和作为外键使用。任何对主键的修改会导致巨大的数据库维护工作量,显然这是不合时宜的设计。就关系数据库而言,OID策略采用的是代理主键的方法。
3.2 OID的唯一性
在分配对象标识符(OID)时,需要考虑两个主要的问题:OID唯一性的级别和如何计算它们。
OID唯一性级别的问题对于面向对象的初级开发者,并不十分明显。OID唯一性具有三种级别:具体类中对象标识唯一;类层次中对象标识唯一;所有类的对象标识均唯一。
例如,考虑下图的类层次模型。假设对Customer对象给定标识OID=74656。则存在以下的可能:
1. 该OID可以分配给Employee,因为Employee与Customer是不同的具体类。
2. 该OID在类层次中唯一,即不能分配给Employee对象。
3. OID在所有类的对象中具有唯一性,从而不能分配给其它任何对象。
该问题其实归结于多态性(Polymorphism):有可能Customer对象会成为Employee对象。此时,如果采用具体类中的唯一性,为了避免OID与Employee的标识的重复,会导致对Customer对象标识的重新分配。而为了避免OID的重新分配问题,至少需要类层次中标识的唯一性,而所有类中对象标识的唯一性可以彻底地解决该问题。
OID至少在类层次中应具有唯一性,最理想的方案是所有对象标识具有唯一性。
3.3 分配OID的策略
第二个问题,是如何决定新的OID,这个问题对应用程序运行的效率有极大的影响。
3.3.1 在整数列上使用max()
在整数列上使用MAX()函数是一种常用的方法。该方法的基本思想:数据库中插入新行时,在主键列上使用MAX()函数取最大值,加1作为主键的新值。
该方法的问题是需要在访问时对列暂时加锁,尽管许多数据库对该操作进行了优化。
这种方法仅在每个表中的对象取得了唯一的对象OID,并没有在所有对象之间保证唯一性。
3.3.2 使用并维护键值表
该策略有两种方法。保存并维护单字段、单行的数据表,用来存储整数计数。当进行插入操作时,对其加锁及增1作为新的OID值。方法的优点在于避免了调用MAX()函数时对整个表的锁定,同时保证了所有表主键的唯一性。缺点则是进行大批量插入操作时,该表会成为性能瓶颈(尽管在内存中,可以对该值进行缓存)以及有效值很快会被耗尽。
另一种方法是,在系统中使用多行键值表,每一行对应一个数据库表。键值表具有两个字段,一个指定相应的表名,另一个用于确定具体的取值。与前面的方法一样,它避免了MAX()函数对整个表的锁定,并且由于为每张表使用了键值,阔宽了OID的范围。然而,它失去了对所有表主键唯一性的强制;键值表仍然可能成为性能的瓶颈。(尽管可以在内存中,对这些值进行缓存)
3.3.3 GUID/UUID
许多年前,Digital使用了一种称之为UUID的策略。该策略基于哈希计算机以太网卡的物理标识和当前时间来得到唯一的128位键值。而对于无以太网卡的计算机,则可以通过在线的文件得到标识数字。Microsoft具有类似的GUID策略来得到128位的字符串。
两种方法均能很好的工作,尽管它们具有平台相关性。另外,不使用Persistence机制产生ID,始终是一个潜在的问题。
3.3.4 Persistence机制提供的功能
许多数据库,如Oracle,能自动的产生唯一的序列值。尽管该方案可以很好的工作,但它们采用了厂商私有的方法,且在定义时确定,从而无法进行有效地控制。如果面临跨平台移植时,可能成为非常严重的问题。
3.3.5 high/low方法
替代使用较大整数来获取OID的方法(要求对单个资源——字段进行访问,从而成为瓶颈):将OID分解为两个逻辑组成部分。在应用程序首次需要创建OID时,向数据库的单个字段请求HIGH值(或者从某些数据的内建函数获得),对于LOW值,初始化为0,在本次会话随后的请求递增。如果LOW值到达了极限,则再次向数据库申请HIGH值。由于HIGH值互斥的获得,进而保证了唯一性。
HIGH/LOW方法的优点,每次会话只需与数据库交互一次,减少了流量,使键值表的访问不再成为瓶颈。其次,保证了OID在所有对象中的唯一性。
与前面所提到的方法比较,HIGH/LOW方法是最有效和实现较简易的方法。
1. HIGH/LOW方法的实现
采用OO方法实现OID策略,使用类封装OID的行为。
图18显示了实现OID的一种方法。其基本思想是在创建永久对象:由ObjectFactory对象(可参见创建设计模式中的Singleton)为它分配OID,ObjectFactory的唯一职责创建新的OID对象。ObjectFactory跟踪HIGH和LOW的取值来得到新的OID。具体而言,通过访问Persistence机制(数据库)来获取HIGH,而基于LOW的当前值返回唯一的OID。asColumns方法则以关系数据库存储的对应形式返回OID的实例。
图19展示了在关系数据库中实现HIGH/LOW OID策略可能的实现方法。没有任何一种是完美的,各有优缺点。重要的是选择一种方案,并对数据库中的表一致地实施,使Persistence层更加容易开发和维护。
1. 为主键定义单个整数。该方法设计简单,但OID的取值范围较小。(0~数据库所允许整数的最大值)
2. 使用任意长度字符串作为主键避免了上述的问题,但增加了运行开销。(许多数据库对整数作为主键进行了优化,而对字符串作为主键的访问较慢)
3. 采用复合主键较方法2减少了存储开销,但仍具有某些弊端,具体讨论参见对象——关系数据库映射的讨论。
4. 结合方法2、3,对OID对象进行哈希操作,将其转换成字符串。基本思想是将OID转化成一系列的8位的数字,接着根据数字构造字符,最后将字符连接成字符串。
推荐方案:
高位(HIGH)采用96位的数字(通常是3个32位的整数),低位(LOW)采用32位的数字。接着,将其转换成128位的字符串。该方案的最大缺点是许多数据库可能对于定长字符串的主键未进行优化。具体的方法则应根据需求来确定。
2. 分布式环境下的HIGH/LOW方法的实现
如前面所提到的,OID必须唯一。在分布式环境中,客户机从众多服务器中取得HIGH值,应如何保证该值的唯一性呢?答案很简单:服务器之间也必须使用HIGH值唯一的策略。与之对应有许多方案,如:服务器使用自己的HIGH/LOW方法,而从唯一的资源处获得HIGH值;或者,服务器从某个集中式的服务得到HIGH值块,在HIGH值分配完之后重新申请新的块。
3. 多厂商数据库环境下的HIGH/LOW方法的实现
另一个相关的问题是,许多大型机构往往使用多种厂商的数据库来实现Persisntence层的设计。尽管各种厂商都有特定的机制来产生代理键值,但在分布式的环境中,大多都只能应用于特定的产品。例如:产品A的机制产生HIGH值1701,产品B也产生了1701的HIGH值。这会导致两难的局面,此时就要求能将HIGH/LOW实现的方法应用到上述环境中,可以使用上节的方法来进行处理。
4.关系数据库常用技术
4.1 索引(Indices)
实现数据库结构的最后一步往往是为其添加索引以优化性能。
通常,需要为每一个主键和侯选键定义唯一性索引。(绝大多数RDBMS在定义主键、侯选键约束的同时创建唯一性索引)同时,还需要为主键和侯选键约束未包容的外键定义索引。
在此,对索引的重要性特别加以强调。主键和外键上的索引能加速对象模型中的访问。(实际上,索引用于两个目的:加速数据库的访问;为主键和侯选键强制唯一性)数据库实现必须添加索引,否则,用户将因不良的性能感到沮丧。在数据库设计的早期,就应并入索引,因为它们的实现非常简易,延迟实现并不是一个很好的主意。
数据库管理员(DBA)可能会为经常使用的查询定义附加的索引。DBA也可能使用特定产品的协调机制。
4.2 存储过程(Stored Procedures)
存储过程是运行在关系数据库服务器端上的函数/过程。尽管SQL代码通常是存储过程的主要组成部分,但大多数厂商使用特有的语言。典型的存储过程运行一些SQL代码,进行数据处理,接着以零行或多行的形式返回,或者显示错误信息。存储过程是现代关系数据库非常强大的工具。
在向关系数据库映射对象时,假设未使用Persistent Layer的前提下,有两种情况使用存储过程较合理。
第一种情况是建立快速的、简陋的、随后将遗弃的原型。存储过程看上去是建立原型最快速的方法。
第二种情况是必须使用已有的数据库。并且,该数据库的设计不适用于对象方法,无法使其为特定的要求服务。则可以适用存储过程,以类似于对象的方法来读写记录。注意,存储过程并不是唯一的方法;相应的,可以使用其它语言来书写,并在数据库外运行代码。(可能仍在Server端,以避免不必要的网络流量)
另一方面,有许多充分的理由不使用存储过程。
使用存储过程,Server端很快成为运行的瓶颈。当某个简单的存储过程被频繁的调用时,会极大地降低数据库的性能。
存储过程一般采用特定于厂商的语言编写。在不同数据库之间进行移植时,甚至在相同数据库的不同版本移植时,会出现很多不可预见的问题。
存储过程极大地增加了与数据库之间的耦合。它降低了数据库管理的灵活性。在修改数据库时,会要求重写存储过程,从而增加了维护的工作量。
底线:存储过程仅仅是一种用于解决短期问题的蹩脚方法。
4.3 触发器(Triggers)
触发器实际上是一种可以自动调用的存储过程。在增、删、改前调用触发器是非常普遍的方法。触发器必须成功运行,否则相应的操作失败。触发器通常用来保证数据库的完整性。
与存储过程一样。触发器使用特定厂商的语言,使它们较难移植。好消息是一些建模工具能基于关系的信息自动生成触发器。只要不修改生成的代码,则可以在跨厂商/版本移植时,为数据模型重新生成触发器。
建议:不使用Trigger,即使在保证数据完整性时。数据完整性往往相关于特定应用域,可以在Business Layer或MCV模型中的View层次实现。