扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
Active Record 模式中文名为“活动记录”,在《企业应用架构模式》(PoEAA)一书中定义如下:
活动记录(Active Record):一个对象,它包装数据库表或视图中的某一行,封装数据库访问,并在这些数据上增加了领域逻辑。
举个例子来说,一个图书数据表,每一条记录就是一本图书的信息。那么采用 Active Record 时,每一本图书就是一个 Active Record 对象实例。
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 对象有多个关联。那么我取出一个对象时,很可能就连带取出了其他不少对象。但这些对象可能根本就是本次操作用不上的。其次,将对象更新到数据库时,也需要对关联的对象进行处理,否则对关联对象的修改就会丢失。
虽然可以用各种技巧来避免这些情况,但毫无疑问需要开发者对 RoR 的 Active Record 很熟悉才行。否则看上去很简单的代码,背后则会是噩梦般的数据库操作。
其次,假设我们要将数据库中每本书的单价减半,那么采用 Active Record 模式时,就必须首先读取所有的记录并实例化为对象,然后更新对象属性,再写回数据库。可想而知这样会有多差的效率。
当然了,实际开发中没有人会这样做。开发者会编写一个单独的方法,用一条 SQL 语句完成对批量数据的更新。但也说明 Active Record 模式不适合批量处理数据,而现实世界中,批量处理数据的需求随处可见。
不过由于 RoR 对开发效率戏剧性的提高,所以对于追求开发效率的项目,RoR 是一个很不错的选择。而且性能上的不足可以通过更新硬件或者配合其他技术手段来改善(例如 FastCGI 通常是运行 RoR 应用的首选)。因此在现实世界中,37signals.com 公司的所有基于 RoR 开发的应用,都获得了良好的性能表现(但是同等的硬件,跑 PHP 开发的同样功能应用是更好还是更差呢?这个问题没有答案)。
许多人将 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 模式也不感兴趣了。
许多开发者都很羡慕 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 一样好用的数据库访问方法吗?那么答案是否定的。
我仔细研究了 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 模式有下列优势:
当然,表数据入口也有相对于 Active Record 不足的地方:
而且 Active Record 存在的一些问题,Table Data Gateway 依然无法避免。最主要的就是表数据入口和表模块都是和数据表一一对应,因此不适用于持久化细粒度对象。不过熟悉 .NET 的开发者应该很容易找到解决办法,那就是以表模块完成大部分业务操作,而细粒度对象仅用于部分操作。这是因为 Microsoft 的开发环境一向都对表数据入口和表模块有着偏好和最好的支持。
不过使用表数据入口,相对于 Active Record 最大的好处就是能够很容易的将业务逻辑操作从表数据入口对象分离到表模块对象中,因此对于更大更复杂的项目,表数据入口配合表模块的方式具有更高的可维护性。
表数据入口封装了针对数据表的操作,而表模块则封装了针对数据表的业务逻辑,两者怎么配合呢?我们就以操作图书记录为例,看看具体如何做。
首先,从 FLEA_Db_TableDataGateway 派生一个类,作为图书表的表数据入口对象,例如 TableBooks。接下来建立一个空白的类,名为 ModuleBooks。
现在我们要统计指定年份的出版的图书。
Step1:在 TableBooks 中增加一个方法 countBooksRange():
countBooksRange() 方法可以统计指定区间的图书总数,所以我们再给 ModuleBooks 增加一个 countBooksByYear() 方法来统计指定年份的图书。
上面的例子虽然简单,但是很清晰的描述了表数据入口如何封装具体的数据库操作,而表模块又如何利用表数据入口的方法提供更高层的接口。如果需要 可运行的示例程序,可以参考 FleaPHP 的 SHOP 示例。这个示例中,Model 目录下就是表模块,而 Table 目录下就是表数据入口。
查看本文来源
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者