第三章:映射到关系数据库

架构模式

架构模式的选择对后续的程序开发有着深远的影响并且难以切换(难以从一种模式重构到另一种),所以必须仔细的选择架构模式。

将SQL语句嵌在逻辑代码中会显得非常的丑陋,DBA也希望能够通过了解SQL语句来决定怎样对数据库进行索引,所有这一切的原因让我们倾向于将访问数据库的SQL语句从领域逻辑中分离出来。

以数据表结构来组织类的结构是一个好主意,这样类和数据表可以一一对应。这些类组成了一个数据表的GatewayGateway主要分为两种,Row Data GatewayTable Data Gateway

Row Data Gateway中,数据表中的每一行对应于一个对象实例,比较自然的符合了面向对象的思想。

Table Data Gateway中,整个数据表只需要一个对应的对象实例。而用来储存数据的则是Record Set,一个通用的数据结构,适合于任何一张表。

即使是非常简单的应用,作者也倾向于使用这两种Gateway中的一种。

Table Data Gateway也适合用来包装存储过程。

如果你使用了Domain Model,当然也可以使用Row Data GatewayTable Data Gateway,但是对作者而言,这两者显然不够用了。在简单的应用中,Domain Model的复杂度非常有限,它们的逻辑和数据库非常接近,通常只是一些增删改查的操作,这时,可以将数据库的操作放入,形成Active Record模式。你也可以这样来理解Active Record模式,从使用Row Data Gateway开始,一旦在Transaction Script中看到重复的代码,将这些逻辑加进来。

但是当领域逻辑变得越来越复杂的时候,Active Record开始变得不适合了。当你吧领域类分割成更小的部分,领域类和表的一对一关系变得失败。因为关系数据库不支持继承,所以使用策略和其他一些OO的模式非常的困难。并且对测试的支持也不是很好。更好的分隔领域逻辑和数据库表的方法是将它们完全的分开。Data Mapper模式负责所有的数据库操作,这样就让领域层和数据库完全的分开了,这也让它成为了最复杂的数据库映射架构。

作者建议使用Gateway模式来作为Domain Model的主要的持久化模式,如果领域逻辑很简单,可以使用Active Record,如果很复杂,则可以使用Data Mapper

这些模式并非必须完全孤立的使用,很多时候是以一种为主要的持久化模式。但是人们通常不愿意同时使用几种,因为这样会搞混。

处理视图时,查询会很容易,但是更新的时候就会有麻烦,可能会导致数据不一致。

虽然本书讲述的模式足够告诉你怎样搭建一个Data Mapper,但是这项工作还是很昂贵的,所以,如果可能的话,买一个O/R mapping工具。这些工具都会提供很多选项,要想知道该怎么配置它们吗,你还是需要了解相关的模式。

行为问题

当人们谈起O/R mapping的时候,通常都会关注结构方面(如何关联表和对象)。然后,作者更关注架构和行为方面。

行为问题是:怎么把不同的对象从数据库中取出和放回。初看好像并没有什么问题,使用Active Record时这是很明显的。但是如果你将一系列的对象载入到内存中并修改了它们,你必须跟踪并记录哪些是你改过的,并确保把它们更新到数据库中。而且当你新建了一些行还修改了另外一些,你需要有相应的关键字段(keys)来修改那些引用它们的行。当你读取了一些对象,并开始修改它们时,你必须保证数据库里的这些数据不被修改,不然就会产生不一致的问题。这个问题将放在第五章中详细的来讲。

主要用来解决上面两个问题的模式是Unit of Work。它跟踪所有从数据库中读出的对象和所有修改过的对象。它还负责更新数据库,这样程序员就不用显示的调用保存方法,只需要提交(Commit)这个unit of work就可以了。Unit of Work可以看成一个和数据库映射层打交道的控制器对象(object that acts as the controller of the database mapping),没有Unit of Work,领域层担当了控制器的角色,决定什么时候读写数据库。使用了Unit of Work将控制器从领域逻辑中抽了出来。

当载入对象时,小心应付一个对象被载入两次的情况。如果你这样做,你会有两个对应于同一个数据库行的对象。这样的话你就要保持它们的同步,这将会很混乱。为了处理这种情况,你必须在Identity Map中保持每个读进内存对象的记录,每次读取数据时,先检查Identity MapIdentity Map也起了数据库缓存的作用,但是记住,Identity Map并不是用来改善性能的。

当你使用Domain Model时,你可能会想一次读入多个相关对象,如,读入一个订单对象时将用户对象也一并读进来。然而

对象的关系是如此之多,以至于读入一个对象会牵涉到很多相关的对象。为了避免这点,可以使用Lazy Load,它为关联对象使用一个占位符。这个主题有很多的变体,但是都是使用占位符的方法来实现的。只有当你想要使用这个对象时,才会从数据库中读取这个对象。

读取数据

作者倾向于使用finder方法来读取数据,如find(id),findForCustomer(customer),这些方法包装了一个Sql查询语句。

这些finder方法放在哪里是由模式来决定的,如果你使用的是基于表的模式--对于每个表都有一个对象的实例与它对应--你可以把这些方法和插入/更新方法放在一起;如果你使用的时基于行的模式,你可以将finder函数作为静态函数,但是这样你就无法替换它们了,这也就意味着你无法用存根类(Service Stub)替换它们来进行测试了。为了解决这个问题,你可以使用一个独立的finder对象,查询时返回一个对象的列表。

finder方法一个值得注意的地方是它们以数据库状态为准,而不是对象状态,这就是说你查询出来的结果不会包含你已经在内存中加入,但是还没有保存到数据库中的对象。

读取数据时,性能都是一个会显现的话题。这儿有一些简单的规则。

一次查询多条,而不是分多次查询,多查通查比少查好(当然还得看实际情况)。

使用join,可以一次查询更多的记录,这样做的时候你通常需要一个Gateway或者Data Mapper。使用join的时候,记住数据库是为三或四个join做优化的,更多的join只能降低性能。当然使用视图来代替常用的join是很好的办法。

对数据库来说,可做的优化很多,如将相关的数据组织在一起,小心的使用索引,使用好数据库的缓存功能,更多的方法,可以询问DBA。

优化时一定要以你的数据库和数据为基准进行测试,上面这些只是一些通常是有效的指导规则,引导你思考而已。

posted on 2006-12-18 20:29  tmfc  阅读(3578)  评论(7编辑  收藏  举报