(十七)从 SQL 操作接到 ORM、会话与迁移
1. 对象映射与会话
目前从 SQL 行中取出字段,再手动建立 Book。对象关系映射(ORM)会把类、属性与表列的对应关系写成模型,让工具生成查询和更新语句。它仍要处理刚才出现的键、关联、连接与重复聚合,不会因为返回了对象就改变数据关系。
以 SQLAlchemy 的会话为例,同一会话可以按主键跟踪同一行对应的对象,记录待写入的新对象或字段变化。flush 把待处理变化送成 SQL,事务随后还可能回滚;commit 才提交事务。数据库连接负责通信与事务,会话还负责对象身份与变化跟踪,这两层职责分开阅读。SQLAlchemy 会话
例如连续查出同一编号时,会话可以继续关联同一个对象;若每次都手动从行建立新实例,就得到不同的 Python 对象。理解第四章的对象身份、别名和修改动作后,再看会话保存的状态会更清楚。
2. 模型改变后,已有表也要升级
给 Python 类增加字段,不会自动给已有数据库加一列。迁移记录描述怎样把旧结构改成新结构,同时保留已有数据。先用一个版本号观察最小的升级过程:
# user_version 保存应用自己的结构版本;版本 0 升级时增加 genre 字段。
from contextlib import closing
import sqlite3
def upgrade(connection):
version = connection.execute("PRAGMA user_version").fetchone()[0]
if version == 0:
with connection:
connection.execute("ALTER TABLE books ADD COLUMN genre TEXT NOT NULL DEFAULT ''")
connection.execute("PRAGMA user_version = 1")
with closing(sqlite3.connect(":memory:", autocommit=False)) as connection:
with connection:
connection.execute("CREATE TABLE books(title TEXT)")
connection.execute("INSERT INTO books VALUES (?)", ("Python",))
upgrade(connection)
upgrade(connection)
print(connection.execute("PRAGMA user_version").fetchone()[0])
print(connection.execute("SELECT title, genre FROM books").fetchall())输出 1 与 [('Python', '')]。第二次检查已经是版本 1,不会再次加列;旧记录继续存在,并取得新增字段的默认值。实际迁移工具把这些变化整理成有顺序的脚本,并记录当前到达哪个版本。Web 章节会结合 ORM 与 Alembic 或框架自己的迁移系统使用。迁移脚本与版本