最近,我见到了很多针对 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 是非常有帮助的。