最近,我见到了很多针对 ORM 的抨击,但是我觉得有些批评是莫须有的。我本人就是 SQLAlchemy 的忠实拥趸。在我的项目里很多地方都用到了 SQLAlchemy,我也为 SQLAlchemy 项目贡献了一些代码。这篇文章里,我会阐述你应当爱上 SQLAlchemy 的10个理由。说实话,除了 SQLAlchemy 以外还有很多优秀的 ORM,我所阐述的大部分理由同样适用于它们。但是 SQLAlchemy 是我的最爱。

1、用应用程序代码定义数据库模式

SQLAlchemy 允许你使用 Python 代码来定义数据库模式(schema)。下面是一个电子商务网站的例子,一个订单就代表一条记录。

class OrderItem(Base):

    id = Column(Integer, primary_key=True)

    order = many_to_one('Order')

    product = many_to_one('Product')

    quantity = Column(Integer)

定义数据库模式的 Python 代码在 SQLAlchemy 中叫做模型(model)。因为这些模型都是用 Python 的类实现的,所以你可以添加自己的类方法。这样可以把相关功能放在一起,方便维护。

class Order(Base):

    ...

    def update_stock(self):

        for item in self.items:

            item.product.stock -= item.quantity

上面这个例子说明了,在 SQLAlchemy 中数据库的模式也可以通过版本控制来维护。所以使用SQLAlchemy 的同时,你也可以享受到版本控制的诸多便利:比如版本追踪、标签、追溯(blame)等等。

2、自动同步模型到数据库模式

Alembic 是 SQLAlchemy 的一个数据库管理插件。当你修改模型的时候,Alembic 可以自动更新数据库模式。使用 Alembic 来做一些添加表或者列的小改动是非常便捷快速的。

$ alembic upgrade head

INFO  [alembic.context] Context class PostgresqlContext.

INFO  [alembic.context] Will assume transactional DDL.

INFO  [alembic.context] Running upgrade None -> 1975ea83b712

尽管在开发环境中自动同步非常方便,但是大多数人还是想在生产环境中采用更稳妥一些的方式。这一点 Alembic 也想到了,它可以自动生成修改的脚本,数据库管理员可以在检视脚本之后再应用到生产环境的数据库上。

3、Pythonic 的代码风格让你的代码更易读

SQLAlchemy 使用更 Pythonic 的方式表示数据库关系,这对于阅读和编写代码来说是非常方便的。我们来看下面这个例子,这个例子可以打印出在一个订单中的所有产品。

for item in order.items:

    print(item.product.name, item.quantity)

代码非常的简单易读,但是打通了两个数据库,执行了 JOIN 连接查询。order.items 是一对多的关系,SQLAlchemy 会自动加载 OrderItem 中和这个订单相关的对象。item.product 则储存的是多对一的关系,SQLAlchemy 会自动加载对应的产品。

SQLAlchemy 也可以运用类。如果应用修改了一个有映射的字段,对象会自动请求写入数据库。这个功能使编写应用逻辑的时候没有了后顾之忧。

4、用 Python 构建查询语句

类似用主键获取对象这种简单查询只需要很少的代码:

order = session.query(Order).get(order_id)

使用 Python 的查询语法,我们可以实现更复杂的查询。比如下面的例子,我要查找两天以前的有效订单:

overdue_orders = session.query(Order).filter(Order.status == 'active'

            && Order.time < datetime.now() - timedelta(days=2))

SQLAlchemy 的语法可以让你把 SQL 语句和 Python 的变量结合起来,并且可以杜绝 SQL 注入***。SQLAlchemy 会在内部重载各种比较运算符,然后将它们转换成 SQL 语句。

当你执行一条非常复杂的查询时,使用 SQLAlchemy 语法来定义查询也是可以的。但是我认为 SQLAlchemy 可以胜任的查询复杂程度是有限的,某些时候直接写 SQL 语句可能更容易些。在这种情况下,你可以定义数据库视图来完成复杂查询,SQLAlchemy 可以把视图映射到 Python 对象。

5、与 Web 框架的无缝集成

有些框架默认支持SQLALchemy ,比如说 Pyramid。对于其他 web 框架,你需要安装一个集成库来支持 SQLAlchemy,比如说用于 Flask 的 Flask-SQLAlchemy 或者用于 Django 的 aldjemy。

SQLAlchemy 会维持一个连接池,为每一个 web 请求提供一个可用的数据库连接。那些支持库可以处理常见异常,提高应用的健壮性,让应用在某些异常情况下不至于崩溃,比如运行时重启数据库这样的操作。

每一个请求都会用事务包裹起来,如果请求成功就提交事务,否则就回滚。这种设计使得外部方法能够正确地与数据库交互,而不需要关心具体的数据库处理代码。

6、预先加载提高性能

大部分 ORM 都使用的是延迟加载策略。在第一次调用关系时,一次 SQL 查询会执行,加载数据。像上面的例子,order.items 的调用实际上执行了一次 SQL 查询,之后的每次使用 item.product 都会发起另外的查询。因为 item.product 是在一个循环中调用的,所以会生成大量的 SQL 查询,导致性能降低。这种情况叫做“n+1 选择问题”。

SQLAlchemy 针对上述问题有一个解决方案:预先加载(eager loading)。当我们第一次加载 Order 这个对象时,我们可以通知 SQLAlchemy 我们会使用那些关系,然后 SQLAlchemy 就能在一次查询中加载所有数据,语法如下所示:

session.query(Order).options(joinedload_all('items.product')).get(order_id)

7、透明的多态支持

像 Python 这样的面向对象语言是鼓励使用多态的。如果有一个 Person 的基类,我们就可以基于 Person 来创建子类,比如 增加了新字段的Employee 或者 Customer 类。但是传统的 SQL 数据库不支持多态,所以 ORM 想要支持多态也是有心无力。

但是 SQLAlchemy 在 SQL 中完美地模拟了多态。我们在 Python 代码中可以非常自然地使用多态,数据库中的数据也可以通过 SQL 便捷地获取。在应用代码中我们可以轻松地使用多态类,而不需要关心它们究竟是怎么存储的。

8、兼容已有数据库

一些 ORM 要求你的数据库结构满足既定条件,强制每张表都有单一主键列,甚至主键名称必须是 “id”。如果你从头建立一个数据库,这些限制并不是问题。但是如果你想用以前的数据库,这些限制条件会阻止你访问那些不满足条件的表。

SQLAlchemy 不会假定你的数据库结构,所以可以完美支持以前的数据库。还有一个叫做 sqlacodegen 的工具可以根据已有的数据库生成 SQLAlchemy 模型。SQLAlchemy 使得你可以通过简单的 Python 脚本和以前的数据库进行交互。

9、提供许多钩子函数让你自定义库

SQLAlchemy 拥有清晰的分层架构。几乎任何 SQLAlchemy 库都可以被重写以满足特定需求。

当在有多个使用者的云应用上工作的时候,我发现了一个非常有用的功能。比如说应用中大多数查询都包含了过滤条件,只返回当前使用者的结果。就像下面这个例子:

products = session.query(Product).filter(Product.merchant == current_user.merchant).all()

但是我发现,如果一不小心忘记加过滤条件的话,可能让某个用户可以看到其他经销商的信息,而这是不允许的。谨慎起见,我们可以创建一个自定义的 SQLAlchemy 的 session 工厂函数,可以在会话中自动给所有查询应用过滤条件。虽然只是这么一点小小的控制代码,却可以让你的应用安全系数更高。

10、完善的文档

有些开源项目的文档的确是漏洞百出,但是 SQLAlchemy 不是这样。SQLAlchemy 的文档非常详尽,并且还有从简单例子到高级特性的学习指南,以及全面的 API 参考文档。这对于开发人员学习和使用 SQLAlchemy 是非常有帮助的。