`
happmaoo
  • 浏览: 4591862 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

为什么 FleaPHP 使用 Table Data Gateway 代替 Active Record 来提供数据库访问服务

阅读更多
<iframe align="center" marginwidth="0" marginheight="0" src="http://www.zealware.com/csdnblog336280.html" frameborder="0" width="336" scrolling="no" height="280"></iframe>

[原文地址:http://www.dualface.com/blog/?p=357]

  许多开发者很疑惑为什么 FleaPHP 以高效开发为目标,却没有提供 Active Record 模式。本文尝试详细阐述这个问题。

Active Record 是什么?

  Active Record 模式中文名为“活动记录”,在《企业应用架构模式》(PoEAA)一书中定义如下:

活动记录(Active Record):一个对象,它包装数据库表或视图中的某一行,封装数据库访问,并在这些数据上增加了领域逻辑。

  举个例子来说,一个图书数据表,每一条记录就是一本图书的信息。那么采用 Active Record 时,每一本图书就是一个 Active Record 对象实例。

Active Record 因 Ruby On Rails 而流行

  Active Record 之所以现在这么炙手可热,甚至许多人将 Active Record 和 ORM 划等号,完全是 Ruby On Rails 的原因。

在 Ruby On Rails 中,Active Record 除了最基本的将数据记录和一个对象互相映射外,还提供了数据(而不是对象)间关联关系的处理。例如:

  一本图书有一个或者多个作者,所以每一个图书对象都和多个作者对象关联。反过来一个作者可以写多本书,所以一个作者对象也和多个图书对象关联。

  在 RoR 中,我们获取一个图书对象时,自动就获得了该图书对象所对应的作者对象(本质上是图书数据对应的作者数据)。更进一步,通过图书对象关联的作者对象,我们 可以获取该作者所写的所有图书的对象实例。而这些工作,在 RoR 中只需要几行代码而已,以前我们需要写上一大段代码才能实现同样的效果。

  RoR 中,对 Active Record 模式的实现完全利用了 Ruby 语言的灵活性,简短几行代码就可以定义一个关联。并且通过复杂的 ActiveRecord:Base 对象,提供了 CRUD(创建、读取、更新、删除)操作的默认处理。所以使用 RoR 时,绝大部分常见的数据库操作只需要很少量的代码就可以完成,大大提高了开发效率。

  但 Active Record 模式也不是完美的,Active Record 存在不少缺点。

  • Active Record 模式需要数据表结构和对象属性一一对应(至少是大部分对应),否则将难以使用 Active Record 模式;
  • Active Record 模式并不能够真正适合完全面向对象的应用程序。因为 Active Record 模式本质上就要求一个对象必须和一个数据表对应。但在完全面向对象的应用程序中,数据和操作数据的方法很可能分布在各个不同的对象中,这些对象却并没有和 某一个数据表完全对应,而且 Active Record 无法很好的处理对象的继承、聚合等面向对象常见的对象间关系;
  • 随着逐渐向 Active Record 添加业务逻辑,Active Record 对象中会混入越来越多的 SQL 语句,这在更复杂的项目中显然是一个不利因素。

  如果在 Active Record 模式中添加了对数据关系(注意,不是对象关系)的处理,那么还要注意性能问题:
  假如一个 Active Record 对象有多个关联。那么我取出一个对象时,很可能就连带取出了其他不少对象。但这些对象可能根本就是本次操作用不上的。其次,将对象更新到数据库时,也需要对关联的对象进行处理,否则对关联对象的修改就会丢失。

  虽然可以用各种技巧来避免这些情况,但毫无疑问需要开发者对 RoR 的 Active Record 很熟悉才行。否则看上去很简单的代码,背后则会是噩梦般的数据库操作。

  其次,假设我们要将数据库中每本书的单价减半,那么采用 Active Record 模式时,就必须首先读取所有的记录并实例化为对象,然后更新对象属性,再写回数据库。可想而知这样会有多差的效率。
  当然了,实际开发中没有人会这样做。开发者会编写一个单独的方法,用一条 SQL 语句完成对批量数据的更新。但也说明 Active Record 模式不适合批量处理数据,而现实世界中,批量处理数据的需求随处可见。

  不过由于 RoR 对开发效率戏剧性的提高,所以对于追求开发效率的项目,RoR 是一个很不错的选择。而且性能上的不足可以通过更新硬件或者配合其他技术手段来改善(例如 FastCGI 通常是运行 RoR 应用的首选)。因此在现实世界中,37signals.com 公司的所有基于 RoR 开发的应用,都获得了良好的性能表现(但是同等的硬件,跑 PHP 开发的同样功能应用是更好还是更差呢?这个问题没有答案)。

Active Record 与 ORM

  许多人将 Active Record 与 ORM 划等号,这是错误的。ORM(对象关系映射)是将对象及对象间的关系(继承、聚合等)映射到关系式数据库中。由于面向对象和关系式数据库天生的不匹配,所以这种映射是相当复杂的。

  而 Active Record 原本只是将一个数据行记录包装为一个对象,只是在 RoR 中由于添加了对关系的处理,而具有了一些 ORM 的特征。所以可以简单的将 RoR 中的 Active Record 看作 ORM 的一种实现方式。但本质上,RoR 中的 Active Record 是处理数据间的关系而不是对象间的关系(但支持对象继承),因为每一个 Active Record 对象都是和数据表一一对应的。

  那为什么在 Java 世界中,没有大量采用 Active Record 模式呢?

  在 Java 世界中,绝大部分 ORM 都是作为中间件存在的。由于 Java 与 Ruby、PHP 等脚本语言截然不同的运行机制。所以即便是很复杂的中间层,只要能够在运行时提供良好的性能,那就能够被开发者接受。而 Hibernate 这样的 ORM 中间件能够提供比 Active Record 多得多的功能和灵活性,所以 Active Record 模式在 Java 世界不受欢迎就可以理解了。

  而在 .NET 世界中,大量使用的都是表数据入口(Table Data Gateway)和表模块(Table Module)。这两种模式由于有 Microsoft 出色的 IDE 支持,所以能够获得很高的开发效率,自然 .NET 开发者对 Active Record 模式也不感兴趣了。

如果将 Active Record 或者 ORM 照搬到 PHP 中呢?

  许多开发者都很羡慕 Hibernate 的强大功能和 RoR 中 Active Record 的快速开发能力,但是这些东西如果照搬到 PHP 中,会遇到一个相当大的麻烦:

  PHP 本质上是解释执行的脚本语言,所以对于每一次 HTTP 请求,PHP 执行环境都会将请求的 .php 文件编译为 opcode,然后执行 opcode,再清理所有的资源(内存、数据库连接、文件句柄等等)。在这种环境中,应用程序应该花尽可能少的时间去初始化底层框架,而是把大部分资源用 在业务逻辑的执行上。

  但 Ruby 也是解释执行,为什么就可以用 Active Record,而 PHP 就不应该呢?
简单点说就是因为 PHP 在面向对象支持上的缺陷使得要实现和 RoR 同等功能的 Active Record 模式变得非常艰难。也许你对此不以为然,那么可以实际尝试一下使用 PHP on Trax(一个 RoR 的 PHP 克隆)。看看一次简单的读取操作需要载入多少文件并调用多少对象和方法。

  所以有些 PHP 框架提供的 Active Record 模式实现非常简单,根本不考虑关联问题,但这样一来使用 Active Record 能获得的开发效率提升就太小了。

  至于更为复杂的 ORM,目前 PHP 领域还没有一个真正的成功项目。虽然 Propel 是目前 PHP 领域唯一一个具有实际工作能力的 ORM。但由于其自身的复杂性和执行效率问题,一直没有得到广泛使用。即便是 Symfony 也是对 Propel 进行裁剪后才用于处理数据库操作。

虽然在国内 PHP 社区中常看到有人说自己做的 ORM 如何如何先进,既有高级特征,又有好的效率。但自始至终没有看到过有人公布代码。至于不公布的原因不外乎:还不够成熟,成熟后再公布;我是最领先的,除非 有了同水平的,不然我不会公布;商业产品,不能泄露。而且别说是代码,就算问问实现原理通常也只能得到几句无关痛痒的回答。

所以如果你看到这篇文章后,觉得你实现了我认为很难实现的东西,请拿出实际证据。不要再搬出诸如此类的理由,没有论据的辩论是毫无意义的。

  那么 PHP 就注定和 Active Record 和 ORM 无源吗?
如果这个问题的潜在意思是问:PHP 就不能找到和 Active Record 一样好用的数据库访问方法吗?那么答案是否定的。

Table Data Gateway 是一个更合理的选择

  我仔细研究了 PoEAA 中关于表数据入口、表模块的内容后,又做了大量实际测试。最终决定在 FleaPHP 中采用 Table Data Gateway(表数据入口)模式来提供数据库服务。并在此基础上实现对关联数据的自动处理。

表数据入口(Table Data Gateway):充当数据表访问入口的对象,一个实例处理表中所有的行。

表模块(Table Module):处理某一数据库表或视图中所有行的业务逻辑的一个实例。

  表数据入口是封装一个数据表的操作,而不是一个记录行。这样一来,表数据入口可以很方便的处理针对单个记录和多个记录的操作,而操作的数据就是 PHP 中的数组。实际上我初期还写了一些对象来封装记录集(也就是多行记录),不过后来发现完全是多此一举。PHP 的数组功能非常强大,再专门用对象包装一下弊大于利。

  针对数据表提供单纯的 CRUD 操作吸引力还不够,所以我在表数据入口的基础上增加了对 HasOne、HasMany、ManyToMany 以及 BelongsTo 关联的处理。这四种关联,基本上满足了常见的数据关联操作。

  不过有了自动化的关联,类似 RoR ActiveRecord 中加载过量数据的问题依然存在,所以 FleaPHP 的表数据入口对象 FLEA_Db_TableDataGateway 也提供了针对关联的方法,让开发者可以细粒度的控制数据库操作。

  而且由于表数据入口是针对纯数据进行操作,而不是针对包装了数据的对象。所以开发者可以很容易的优化数据库操作,例如无需读取即可更新数据或者一次性处理大批量的数据。

  相对于 Active Record 模式,Table Data Gateway 模式有下列优势:

  • 表数据入口针对一个表封装数据库操作,这更接近传统 PHP 开发的思维模式;
  • 处理批量数据时,表数据入口更方便,常见操作无需额外编写处理方法;
  • 数据以数组的形式保存和传递,比将每个记录行实例化为对象具有好得多的性能;
  • 实现比 Active Record 简单,每个操作执行更少的代码;
  • 可以很好的与表模块(Table Module)模式配合来封装业务逻辑。从而避免了 Active Record 中将数据库操作和业务逻辑写在一起的问题。

  当然,表数据入口也有相对于 Active Record 不足的地方:

  • 由于表数据入口总是传递纯数据,所以无法像 Active Record 一样以属性的形式封装对数据的操作。不过这种操作即便使用 Active Record 也要多写不少处理代码,而使用表数据入口时,这部分代码只不过是转移到了表模块中;
  • 看上去更没有那么面向对象。可惜的是即便采用 Active Record,大多数应用程序从设计思想上也不是面向对象的,只不过用了一个对象来传递数据而已。

  而且 Active Record 存在的一些问题,Table Data Gateway 依然无法避免。最主要的就是表数据入口和表模块都是和数据表一一对应,因此不适用于持久化细粒度对象。不过熟悉 .NET 的开发者应该很容易找到解决办法,那就是以表模块完成大部分业务操作,而细粒度对象仅用于部分操作。这是因为 Microsoft 的开发环境一向都对表数据入口和表模块有着偏好和最好的支持。

  不过使用表数据入口,相对于 Active Record 最大的好处就是能够很容易的将业务逻辑操作从表数据入口对象分离到表模块对象中,因此对于更大更复杂的项目,表数据入口配合表模块的方式具有更高的可维护性。

表数据入口和表模块的配合

  表数据入口封装了针对数据表的操作,而表模块则封装了针对数据表的业务逻辑,两者怎么配合呢?我们就以操作图书记录为例,看看具体如何做。

  首先,从 FLEA_Db_TableDataGateway 派生一个类,作为图书表的表数据入口对象,例如 TableBooks。接下来建立一个空白的类,名为 ModuleBooks。

  1. classTableBooks extends FLEA_Db_TableDataGateway
  2. {
  3. // 只需要指明数据表名称和主键字段名即可,CRUD 操作已经有了默认实现
  4. var$tableName = 'books';
  5. var$primaryKey = 'book_id';
  6. }
  7. classModuleBooks
  8. {
  9. var$table;
  10. }

  现在我们要统计指定年份的出版的图书。

Step1:在 TableBooks 中增加一个方法 countBooksRange():

  1. classTableBooks extends FLEA_Db_TableDataGateway
  2. {
  3. ......
  4. /**
  5. * 统计指定时间区间的图书总数
  6. */
  7. functioncountBooksRange($begin, $end)
  8. {
  9. // 对参数进行转义,确保不会存在 SQL 攻击漏洞
  10. $begin = $this->_dbo->qstr($begin);
  11. $end = $this->_dbo->qstr($end);
  12. return$this->findCount("publish_date >= {$begin} AND publish_date ");
  13. }
  14. }

  countBooksRange() 方法可以统计指定区间的图书总数,所以我们再给 ModuleBooks 增加一个 countBooksByYear() 方法来统计指定年份的图书。

  1. classModuleBooks
  2. {
  3. ......
  4. functioncountBooksByYear($year)
  5. {
  6. $begin = date("{$year}/1/1");
  7. $end = date("{$year}/12/31");
  8. return$this->table->countBooksRange($begin, $end);
  9. }
  10. }

  上面的例子虽然简单,但是很清晰的描述了表数据入口如何封装具体的数据库操作,而表模块又如何利用表数据入口的方法提供更高层的接口。如果需要 可运行的示例程序,可以参考 FleaPHP 的 SHOP 示例。这个示例中,Model 目录下就是表模块,而 Table 目录下就是表数据入口。




分享到:
评论

相关推荐

    Fleaphp框架使用规范收集.pdf

    - 数据访问层(Data Gateway):在MVC架构中,数据访问层封装了与数据库交互的所有细节,Fleaphp框架使用TableDataGateway类来实现这一点,支持CRUD(创建、读取、更新、删除)操作。 - 视图渲染:框架支持Smarty...

    fleaphp核心完整代码

    5. **数据库操作**:Fleaphp提供了数据库抽象层,支持多种数据库如MySQL、SQLite、PostgreSQL等。它使用ORM(对象关系映射)技术,使得数据库操作更加面向对象,降低数据库依赖性。 6. **模板引擎**:Fleaphp的模板...

    fleaphp 框架实例以及详细使用方法说明

    Fleaphp提供了Active Record模式,使得数据库操作更加直观。 4. **视图(View)** 视图主要用于展示用户界面。Fleaphp支持模板引擎,例如Smarty或Twig,也可以直接使用PHP编写视图文件。视图文件通常位于`views`...

    FleaPHP 框架学习教程.pdf

    - 使用`FLEA_Db_TableDataGateway`类简化数据库操作。 - 数据通过数组形式存储和传递,避免了每个记录封装为对象导致的性能问题。 3. **最少的配置需求** - 只需少量配置即可运行。 - 所有配置使用PHP数组存储...

    fleaphp框架

    7. **缓存机制**:为了提高性能,FleaPHP支持多种缓存策略,如文件缓存、内存缓存(如APC),可以有效减少数据库访问。 8. **安全防护**:框架内置了一些安全措施,如防止SQL注入、XSS攻击,确保应用的安全性。 9....

    php框架fleaphp

    - **运行与调试**:启动服务器,访问应用,利用Fleaphp提供的错误和日志机制进行调试。 4. **拓展与优化** - **插件与扩展**:Fleaphp允许开发者编写插件,扩展框架功能,或集成第三方服务。 - **性能优化**:...

    fleaphp中文手册详细版

    这个“fleaphp中文手册详细版”是为那些希望学习或深入了解Fleaphp的人准备的宝贵资源,特别适合初学者。下面,我们将深入探讨Fleaphp框架的核心概念、特性以及如何使用它进行开发。 一、Fleaphp框架概述 Fleaphp...

    fleaphp1.7.1524

    由于官方可能不再提供这个版本的直接下载,因此这个压缩包为用户提供了方便,使得他们能够获取到这个已过时但可能仍然有需求的软件版本。 在Fleaphp 1.7.1524中,我们可以探讨以下几个关键知识点: 1. **轻量级...

    Fleaphp 常用方法.doc

    这个对象提供了统一的数据库访问接口,无论你使用的是哪种数据库系统。`$dsn`参数可以是数组或字符串,用于定义数据库连接信息。例如,创建一个到MySQL数据库的连接: ```php $dsn = array( 'driver' =&gt; 'mysql',...

    Fleaphp常见函数功能与用法示例

    1. `FLEA_Db_TableDataGateway::update()` 这个函数用于更新指定ID的记录。在示例中,我们看到如何通过传递一个包含字段和新值的关联数组来修改记录。例如,更新uid为22的记录,将name字段设为"11",pass字段设为...

    FleaPHP+开发指南

    - **TableDataGateway模式**:FleaPHP采用此模式封装数据表操作,不仅提供了易用的CRUD接口,还支持数据表间的关联操作。 - **数据处理方式**:区别于其他框架将每条记录封装为对象的方式,FleaPHP利用PHP数组存储...

    fleaphp框架附实例(最新版)

    Fleaphp的核心设计理念是“简洁、实用”,为开发者提供了一套高效的开发工具集,使他们能够快速构建动态、数据库驱动的Web应用。 在“fleaphp框架附实例(最新版)”中,我们可以期待获得以下几方面的知识点: 1. **...

    fleaphp中文手册

    4. **数据库支持**:FleaPHP提供了数据库抽象层,支持多种数据库系统如MySQL、SQLite、PostgreSQL等,通过统一的API进行数据操作,降低了数据库迁移的难度。 5. **模板引擎**:内置模板引擎,支持动态变量、控制...

    fleaphp常用方法分页之Pager使用方法

    如果是字符串(假设为SQL),则需要我们手动设置总记录数,并且可以使用`setDBO()`方法自定义数据库访问对象,如果不设置,则会使用默认的数据库访问对象。 使用`FLEA_Helper_Pager`的示例代码如下: ```php ...

    FleaPHP的单入口文件详解

    FleaPHP可能使用依赖注入或服务容器来管理这些组件,确保它们按需创建和共享。 4. **路由处理**:根据解析后的URL和配置中的路由规则,FleaPHP会确定应执行的控制器和方法。路由可以是预定义的,也可以动态生成,以...

    adodb手册,fleaPHP手册,mysql手册,smarty手册,zendframeworks手册,正则手册

    ADOdb(Active Record Object Data Base)是一个流行的PHP数据库抽象层,支持多种数据库系统。手册通常会包含如何连接和查询数据库、事务处理、记录集操作、性能优化以及错误处理等内容。它可以帮助开发者更有效地...

    fleaphp手册

    fleaphp.chm手册

    FleaPHP框架数据库查询条件($conditions)写法总结

    FleaPHP框架是针对PHP开发的一套轻量级框架,其核心特点是小巧、灵活。FleaPHP框架在数据库操作上提供了一套简洁的API,使得开发者...通过合理使用FleaPHP框架提供的查询工具,可以高效、安全地完成数据库操作任务。

Global site tag (gtag.js) - Google Analytics