Flask SQLAlchemy 数据映射器与活动记录模式
Posted
技术标签:
【中文标题】Flask SQLAlchemy 数据映射器与活动记录模式【英文标题】:Flask SQLAlchemy Data Mapper vs Active Record Pattern 【发布时间】:2017-07-15 19:03:49 【问题描述】:我最近开始研究 Flask 和 Flask-SQLAlchemy。来自 Django 背景,我发现 Flask-SQLAlchmey 非常复杂。我读过 SQLAlchemy 实现了 Data Mapper 模式,而 Django ORM 是基于 Active Record 模式的。
Here 是一个编写的示例代码,它实现了存储库模式来访问数据库。
Here 是 S.Lott(271k 声誉)评论的另一个链接,他说 ORM 是数据访问层,它与模型分离。
我的问题是:
-
您能否提供上述示例中的实际用例或您自己的数据映射器模式有用的示例?我读到的所有地方都是数据映射器模式在复杂情况下很有用,但没有看到示例。
在上述情况下使用存储库模式是否与使用数据映射器模式相同?
数据映射器倡导者是否像示例中那样在与模型不同的类中编写选择查询?
为什么
Question.query.filter_by(text = text).all()
不比使用更好
db.session.query(Question).filter(Question.text == text).all()
?
这不是DataMapper vs ActiveRecord pattern的重复,因为这只是说明定义,我对实际示例更感兴趣。
【问题讨论】:
我建议你阅读本书 (amazon.com.br/…) 从第 44 页开始,Martin Fowler 演示了 Java 中的适用性。 DataMapper的目标是:就是让目标状态远离持久化目标的状态,以及更纯粹的对象 我想一个有趣的映射实际例子是inheritance hierarchies。不确定您将如何使用 AR 实现例如连接表继承,尽管我不太了解 AR。 【参考方案1】:逐点。
1.
我有一个遗留数据库,我必须为其编写一些数据处理实用程序。使用 Mapper 模式,没有 ORM / ActiveRecord 样式,让我在编写查询时像 ActiveRecord 一样简单。它在类似于 SQL 子句的可组合对象上运行,不受 SQL 注入的影响。
“被动”对象允许更大的灵活性/统一性:复杂连接的结果是命名元组,这是简单选择的结果。没有需要关心的身份,没有具有相同身份的缓存对象。
所有更新都是明确的;不是“保存”在其他地方更改的某些状态,没有在 .save()
上运行的挂钩等。这使得有效的批量更新变得微不足道,如果将正确的数据发送到数据库,则不会出现问题。在我的情况下,两者都是好处。在一般情况下,“这取决于”。例如,我必须在插入后手动获取数据库生成的 ID。显式运行此查询需要做一些额外的工作。在我的情况下,能够在一个查询中而不是每条记录中做到这一点是一个巨大的好处。
SQLAlchemy 具有分层设计,即使您在 ORM 上层声明并正常对其进行操作,也允许您访问较低的“映射器”层。例如,在 Django 中,如果/何时仍然可能,这并不那么简单。
2.
在示例中,“存储库”看起来像构建在“映射器”之上的关卡。存储库本可以构建在普通 DBAPI 之上,但映射器使一些事情变得更简单,例如更好的参数绑定、结果集的命名元组以及在普通 SQL 之上的带有可组合、可重用部分的包装器。
映射器还提供了一定程度的数据库独立性。例如。 SQL Server 和 Postgres 有不同的方式来连接字符串; mapper 提供了统一的接口。
3.
您在使用它的地方写下您的select
。如果您有一个在不同上下文中不断重用的选择,则可以将其放入方法或函数中。大多数选择都有一个用途,并且是现场构建的。
SQLAlchemy 设计的一个很好的特点是您可以轻松地存储条件和整个where
子句,并在选择/更新/删除语句中重用它们。
4.
Question.query.filter_by(text = text).all()
使用隐式事务。
db.session.query(Question).filter(Question.text == text).all()
使用显式事务。
显式事务让您安心使用 DML。它们对于select
s 也很重要,当您查询一个快速变化的数据库并希望您的几个相关select
s 看到相同的一致状态时。
我通常在 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
attribute shorthands 使用的线程本地范围会话不同吗?
表示它们共享同一个事务,无论是显式处理还是隐式处理(请求范围)。【参考方案2】:
完全同意上面的回答:是的,SQLAlchemy 的 Data Mapper 模式确实更灵活,对于复杂的查询,它确实更强大,更不神奇,更可控。
但是,在诸如CRUD SQLAlchemy 之类的简单任务中,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 项目!非常“轨道”:)【参考方案3】:我会在 Active Record 上使用 Data Mapper 的唯一原因是如果您有严重的可伸缩性问题。 Data Mapper 鼓励分离域对象和数据库访问逻辑,而 Active Records 将数据库访问逻辑放在域对象中。例如,当您提起 Flask 实例时,它只会按需连接到数据库,而在 Django 中,它将始终连接到数据库。
Data Mapper 将域对象与数据库访问逻辑隔离开来,而 Repository 模式是域对象和 Data Mapper 之间的一层。它比 Data Mapper 高一个级别。例如,在 Data Mapper 模式中,您将拥有简单的 getter 和 setter,在存储库模式中,您将拥有可能还包含一些复杂业务逻辑的 getter 和 setter。
数据映射器与模型类分离。只有 Active Record 模式将 getter 和 setter 加入到同一个类中。
我使用 SQLAlchemy 和 Django 已经有一段时间了,我绝对更喜欢 Django 之类的查询。对于我自己的项目,我在 Django 上使用 Flask + SQLAlchemy 的可能性几乎为零。在考虑这两个框架时,生产力和社区是两个最重要的决定因素。
【讨论】:
以上是关于Flask SQLAlchemy 数据映射器与活动记录模式的主要内容,如果未能解决你的问题,请参考以下文章
SQLAlchemy bulk_insert_mappings():无法获取表“测试”的映射器
Sqlalchemy.exc.UnboundExecutionError:找不到映射器 Mapper|SellsTable|sellers 或此会话上配置的绑定