标签:
原文:http://python.jobbole.com/82453/
本文由 伯乐在线 - Namco 翻译,唐尤华 校稿。未经许可,禁止转载!
英文出处:Paul Johnston。欢迎加入翻译组。
最近,我见到了很多针对 ORM 的抨击,但是我觉得有些批评是莫须有的。我本人就是 SQLAlchemy 的忠实拥趸。在我的项目里很多地方都用到了 SQLAlchemy,我也为 SQLAlchemy 项目贡献了一些代码。这篇文章里,我会阐述你应当爱上 SQLAlchemy 的10个理由。说实话,除了 SQLAlchemy 以外还有很多优秀的 ORM,我所阐述的大部分理由同样适用于它们。但是 SQLAlchemy 是我的最爱。
SQLAlchemy 允许你使用 Python 代码来定义数据库模式(schema)。下面是一个电子商务网站的例子,一个订单就代表一条记录。
1
2
3
4
5
|
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 的类实现的,所以你可以添加自己的类方法。这样可以把相关功能放在一起,方便维护。
1
2
3
4
5
|
class Order(Base):
...
def update_stock(self):
for item in self.items:
item.product.stock -= item.quantity
|
上面这个例子说明了,在 SQLAlchemy 中数据库的模式也可以通过版本控制来维护。所以使用SQLAlchemy 的同时,你也可以享受到版本控制的诸多便利:比如版本追踪、标签、追溯(blame)等等。
Alembic 是 SQLAlchemy 的一个数据库管理插件。当你修改模型的时候,Alembic 可以自动更新数据库模式。使用 Alembic 来做一些添加表或者列的小改动是非常便捷快速的。
1
2
3
4
|
$ alembic upgrade head
INFO [alembic.context] Context class PostgresqlContext.
INFO [alembic.context] Will assume transactional DDL.
INFO [alembic.context] Running upgrade None -> 1975ea83b712
|
尽管在开发环境中自动同步非常方便,但是大多数人还是想在生产环境中采用更稳妥一些的方式。这一点 Alembic 也想到了,它可以自动生成修改的脚本,数据库管理员可以在检视脚本之后再应用到生产环境的数据库上。
SQLAlchemy 使用更 Pythonic 的方式表示数据库关系,这对于阅读和编写代码来说是非常方便的。我们来看下面这个例子,这个例子可以打印出在一个订单中的所有产品。
1
2
|
for item in order.items:
print(item.product.name, item.quantity)
|
代码非常的简单易读,但是打通了两个数据库,执行了 JOIN 连接查询。order.items
是一对多的关系,SQLAlchemy 会自动加载 OrderItem 中和这个订单相关的对象。item.product
则储存的是多对一的关系,SQLAlchemy 会自动加载对应的产品。
SQLAlchemy 也可以运用类。如果应用修改了一个有映射的字段,对象会自动请求写入数据库。这个功能使编写应用逻辑的时候没有了后顾之忧。
类似用主键获取对象这种简单查询只需要很少的代码:
1
|
order = session.query(Order).get(order_id)
|
使用 Python 的查询语法,我们可以实现更复杂的查询。比如下面的例子,我要查找两天以前的有效订单:
1
2
|
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 对象。
有些框架默认支持SQLALchemy ,比如说 Pyramid。对于其他 web 框架,你需要安装一个集成库来支持 SQLAlchemy,比如说用于 Flask 的 Flask-SQLAlchemy 或者用于 Django 的 aldjemy。
SQLAlchemy 会维持一个连接池,为每一个 web 请求提供一个可用的数据库连接。那些支持库可以处理常见异常,提高应用的健壮性,让应用在某些异常情况下不至于崩溃,比如运行时重启数据库这样的操作。
每一个请求都会用事务包裹起来,如果请求成功就提交事务,否则就回滚。这种设计使得外部方法能够正确地与数据库交互,而不需要关心具体的数据库处理代码。
大部分 ORM 都使用的是延迟加载策略。在第一次调用关系时,一次 SQL 查询会执行,加载数据。像上面的例子,order.items
的调用实际上执行了一次 SQL 查询,之后的每次使用 item.product
都会发起另外的查询。因为 item.product
是在一个循环中调用的,所以会生成大量的 SQL 查询,导致性能降低。这种情况叫做“n+1 选择问题”。
SQLAlchemy 针对上述问题有一个解决方案:预先加载(eager loading)。当我们第一次加载 Order
这个对象时,我们可以通知 SQLAlchemy 我们会使用那些关系,然后 SQLAlchemy 就能在一次查询中加载所有数据,语法如下所示:
1
|
session.query(Order).options(joinedload_all(‘items.product‘)).get(order_id)
|
像 Python 这样的面向对象语言是鼓励使用多态的。如果有一个 Person
的基类,我们就可以基于 Person
来创建子类,比如 增加了新字段的Employee
或者 Customer
类。但是传统的 SQL 数据库不支持多态,所以 ORM 想要支持多态也是有心无力。
但是 SQLAlchemy 在 SQL 中完美地模拟了多态。我们在 Python 代码中可以非常自然地使用多态,数据库中的数据也可以通过 SQL 便捷地获取。在应用代码中我们可以轻松地使用多态类,而不需要关心它们究竟是怎么存储的。
一些 ORM 要求你的数据库结构满足既定条件,强制每张表都有单一主键列,甚至主键名称必须是 “id”。如果你从头建立一个数据库,这些限制并不是问题。但是如果你想用以前的数据库,这些限制条件会阻止你访问那些不满足条件的表。
SQLAlchemy 不会假定你的数据库结构,所以可以完美支持以前的数据库。还有一个叫做 sqlacodegen 的工具可以根据已有的数据库生成 SQLAlchemy 模型。SQLAlchemy 使得你可以通过简单的 Python 脚本和以前的数据库进行交互。
SQLAlchemy 拥有清晰的分层架构。几乎任何 SQLAlchemy 库都可以被重写以满足特定需求。
当在有多个使用者的云应用上工作的时候,我发现了一个非常有用的功能。比如说应用中大多数查询都包含了过滤条件,只返回当前使用者的结果。就像下面这个例子:
1
|
products = session.query(Product).filter(Product.merchant == current_user.merchant).all()
|
但是我发现,如果一不小心忘记加过滤条件的话,可能让某个用户可以看到其他经销商的信息,而这是不允许的。谨慎起见,我们可以创建一个自定义的 SQLAlchemy 的 session
工厂函数,可以在会话中自动给所有查询应用过滤条件。虽然只是这么一点小小的控制代码,却可以让你的应用安全系数更高。
有些开源项目的文档的确是漏洞百出,但是 SQLAlchemy 不是这样。SQLAlchemy 的文档非常详尽,并且还有从简单例子到高级特性的学习指南,以及全面的 API 参考文档。这对于开发人员学习和使用 SQLAlchemy 是非常有帮助的。
标签:
原文地址:http://www.cnblogs.com/ajianbeyourself/p/5857631.html