Flask SQLAlchemy 数据映射器与 Active Record 模式

24

我最近开始学习Flask和Flask-SQLAlchemy。作为Django背景下的开发者,我觉得Flask-SQLAlchemy相当复杂。我读到SQLAlchemy实现了数据映射器模式(Data Mapper Pattern),而Django ORM则基于Active Record Pattern。

这里是一个实现仓储库模式(repository pattern)来访问数据库的示例代码。

这里是另一篇评论,作者是S.Lott(271k声望),他说ORM是数据访问层(DAL),而且它与模型分离。

我的问题如下:

  1. 能否提供一个上述示例或自己的示例,说明数据映射器模式何时有用?我读到的所有内容都表明数据映射器模式在复杂情况下非常有用,但没有看到实际的例子。
  2. 在上面的示例中使用仓储库模式是否等同于使用数据映射器模式?
  3. 数据映射器的支持者是否像示例中所做的那样,在不同的类中编写选择查询(select queries)而不是模型中编写?
  4. 为什么使用db.session.query(Question).filter(Question.text == text).all()而不是Question.query.filter_by(text = text).all()好?

这不是DataMapper vs ActiveRecord pattern的重复,因为那只是说明了定义,而我更感兴趣的是实际应用。


我建议你阅读这本书(https://www.amazon.com.br/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420)。从第44页开始,Martin Fowler展示了它在Java中的适用性。DataMapper的目标是:将目标状态与持久目标状态分开,以及使对象更加纯粹。 - Elinaldo Monteiro
我猜映射实际运用中的一个有趣例子是继承层次结构。虽然我不太了解AR,但我不确定如何实现例如连接表继承。 - Ilja Everilä
3个回答

9

逐点分析。

1.

我有一个遗留数据库,需要编写一些数据处理工具。使用Mapper模式(无ORM/ActiveRecord样式),就像使用ActiveRecord一样简单,便于编写查询语句。 它操作着看起来像SQL子句的可组合对象,防止SQL注入攻击。由于对象是“被动”的,所以更具灵活性/一致性:复杂连接的结果是一个命名元组,就像简单选择的结果一样。 不需要关注身份识别,也不会出现具有相同身份的缓存对象。

所有更新都是显式的;不是在其他地方修改状态的“保存”,也没有在 .save() 上运行的钩子等。这使得有效的批量更新变得轻松,而无需担心是否将正确的数据发送到DB中。这在我的情况下都是优点。在一般情况下,“要看情况而定”。例如,我必须手动获取插入后生成的ID。显式运行此查询需要额外的工作。能够在一个查询中完成而不是每个记录一个查询,在我的情况下是巨大的利好。

SQLAlchemy具有分层设计,允许您访问较低级别的“mapper”级别,即使您在较高的ORM级别上声明了某些内容并且通常在其上操作。例如,在Django中,如果/仍然可能存在,则不是那么直截了当。

2.

在这个例子中,“repository”看起来像是在“mapper”之上构建的层级。存储库可以建立在纯DBAPI之上,但Mapper使一些事情变得更简单,例如更美观的参数绑定,结果集的命名元组以及具有可组合,可重用部分的纯SQL包装器。

Mapper还提供了某种程度的数据库独立性。例如,SQL Server和Postgres有不同的字符串拼接方式; Mapper提供了一个统一的接口。

3.

您可以在使用它的地方编写选择语句。如果您在不同的上下文中不断重复使用选择语句,则可以将其放入方法或函数中。大多数选择都只使用一次并现场生成。

SQLAlchemy设计的一个好处是,您可以轻松地存储条件和整个where子句,并在select/update/delete语句中重用它们。

4.

Question.query.filter_by(text = text).all()使用隐式事务。db.session.query(Question).filter(Question.text == text).all()使用显式事务。

显式事务使您在DML上放心。当查询快速更改的数据库并且希望几个相关的选择查看相同的一致状态时,它们对于select也很重要。

我通常在sessionmaker周围编写一个微不足道的包装器,并以此方式编写:

with my_database.transaction() as trans:
   records = trans.query(...)
   ...
   updated = trans.execute(...).rowcount
# Here the transaction commits if all went well.

当我确定这个代码块不应该运行任何DML时,我使用.readonly_transaction(),它总是回滚。

在许多情况下,隐式事务是可以接受的。Django允许您使用@transaction.atomic装饰一个方法,并具有半显式事务控制,在99%的情况下足够了。但有时您需要更细粒度的控制。


在第4点中,db.session不是由Model.query属性简写使用的相同的线程本地作用域会话吗? - Ilja Everilä
这意味着它们共享同一笔交易,无论是显式还是隐式处理(请求范围)。 - Ilja Everilä

7

完全同意上面的答案:是的,SQLAlchemy的数据映射模式确实更灵活,在复杂查询方面更强大,更少神秘,更可控。

但是,在像CRUD这样的简单任务中,SQLAlchemy的代码变得过于臃肿/冗长/重复。

例如,在最简单的“创建”控制器中只需创建一些对象,您需要类似以下的代码:

user = User(name='Nick', surname='Nickson')
session.add(user)
session.flush()

在Active Record ORM中,您只需要一个字符串。

对于简单的任务,有些人可能想要更简单的东西。我的意思是,拥有适用于SQLAlchemy的Active Record将是很棒的。

好消息:我最近创建了这个包(它还包含其他有用的内容)。

看看这个:https://github.com/absent1706/sqlalchemy-mixins


感谢分享你的 Mixins 项目!非常“Rails” :) - Trent

1
  1. 如果你面临着严重的可扩展性问题,我会选择使用 Data Mapper 而非 Active Record。Data Mapper 鼓励将领域对象和数据库访问逻辑分离,而 Active Record 则将数据库访问逻辑放在领域对象中。例如,在 Flask 实例启动时,它只会在需要时连接到数据库,而在 Django 中,它始终会连接到数据库。

  2. Data Mapper 将领域对象与数据库访问逻辑隔离开来,而 Repository 模式是领域对象和 Data Mapper 之间的一层。它比 Data Mapper 更高级。例如,在 Data Mapper 模式中,您将拥有简单直接的 getters 和 setters,而在 repository 模式中,您将拥有可能还包含一些复杂业务逻辑的 getters 和 setters。

  3. Data Mapper 与模型类分离。只有 Active Record 模式将 getters 和 setters 加入到同一类中。

  4. 我已经使用 SQLAlchemy 和 Django 工作了一段时间,我肯定更喜欢 Django 的查询方式。对于我的项目来说,使用 Flask + SQLAlchemy 而不是 Django 的概率几乎为零。在考虑这两个框架时,生产力和社区是最决定性的因素。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接