使用 ORM 进行数据操作

优质
小牛编辑
140浏览
2023-12-01

SQLAlchemy 1.4 / 2.0 Tutorial

此页是 SQLAlchemy 1.4/2.0教程 .

上一页: 使用数据 |下一步: |next|

使用ORM进行数据操作

上一节 使用数据 仍然从核心的角度关注SQL表达式语言,以便在主要的SQL语句结构中提供连续性。本节将构建 Session 以及它如何与这些结构相互作用。

先决条件部分 -本教程以ORM为重点的部分建立在本文档前面两个以ORM为中心的部分的基础上:

使用ORM插入行

使用ORM时 Session 对象负责构造 Insert 在事务中为我们构造并发出它们。我们指导 Session 这样做是通过 添加 对象项;对象项 Session 然后确保这些新条目将在需要时发送到数据库,使用一个称为 脸红 .

类的实例表示行

而在上一个例子中,我们使用Python字典发出了一个INSERT来指示我们想要添加的数据,而ORM则直接使用我们定义的自定义Python类 用ORM定义表元数据 . 在班级层面上 UserAddress 类用作定义相应数据库表的外观的位置。这些类还充当可扩展的数据对象,我们也可以使用这些对象在事务中创建和操作行。下面我们将创建两个 User 对象,每个对象表示要插入的潜在数据库行:

>>> squidward = User(name="squidward", fullname="Squidward Tentacles")
>>> krabs = User(name="ehkrabs", fullname="Eugene H. Krabs")

我们可以使用映射列的名称作为构造函数中的关键字参数来构造这些对象。这可能是因为 User 类包含自动生成的 __init__() ORM映射提供的构造函数,以便我们可以在构造函数中使用列名作为键来创建每个对象。

以类似于我们的核心示例 Insert ,我们没有包含主键(即 id column),因为我们希望利用数据库的自动递增主键特性,在本例中是SQLite,ORM也与之集成。的价值 id 如果我们要查看上述对象的属性,则其自身显示为 None ::

>>> squidward
User(id=None, name='squidward', fullname='Squidward Tentacles')

这个 None 值由SQLAlchemy提供,以指示该属性目前还没有值。SQLAlchemy映射的属性总是在Python中返回一个值,并且不会引发 AttributeError 如果它们丢失,在处理一个没有赋值的新对象时。

现在,我们上面的两个对象被称为 transient -它们不与任何数据库状态相关联,并且尚未与 Session 对象,该对象可以为它们生成INSERT语句。

向会话添加对象

为了逐步演示添加过程,我们将创建一个 Session 不使用上下文管理器(因此我们必须确保稍后关闭它!):

>>> session = Session(engine)

然后将对象添加到 Session 使用 Session.add() 方法。调用此函数时,对象的状态为 pending 尚未插入:

>>> session.add(squidward)
>>> session.add(krabs)

当我们有挂起的对象时,我们可以通过查看 Session 打电话 Session.new ::

>>> session.new
IdentitySet([User(id=None, name='squidward', fullname='Squidward Tentacles'), User(id=None, name='ehkrabs', fullname='Eugene H. Krabs')])

上面的视图使用一个名为 IdentitySet 这本质上是一个Python集合,它在所有情况下对对象标识进行哈希处理(即,使用Python内置的 id() 函数,而不是Python hash() 函数)。

冲洗

这个 Session 使用一种称为 unit of work . 这通常意味着它一次累积一个更改,但在需要之前不会将它们实际传递到数据库。这使得它能够根据给定的一组挂起的更改来更好地决定如何在事务中发出sqldml。当它确实向数据库发出SQL以推出当前的一组更改时,该进程称为 脸红 .

我们可以通过调用 Session.flush() 方法:

>>> session.flush()
BEGIN (implicit)
INSERT INTO user_account (name, fullname) VALUES (?, ?)
[...] ('squidward', 'Squidward Tentacles')
INSERT INTO user_account (name, fullname) VALUES (?, ?)
[...] ('ehkrabs', 'Eugene H. Krabs')

上面我们观察 Session 它首先被调用以发出SQL,因此它创建了一个新事务,并为这两个对象发出适当的INSERT语句。现在的交易 保持打开状态 直到我们打电话给 Session.commit()Session.rollback()Session.close() 方法 Session .

同时 Session.flush() 可用于手动推出当前事务的挂起更改,它通常是不必要的 Session 其特征是 自动冲洗 ,稍后我们将对此进行说明。它还刷新任何时候的更改 Session.commit() 被称为。

自动生成的主键属性

插入行之后,我们创建的两个Python对象处于一种称为 persistent ,它们与 Session 对象,其中添加或加载了它们,并具有许多其他行为,稍后将介绍这些行为。

插入操作的另一个效果是ORM为每个新对象检索了新的主键标识符;在内部它通常使用相同的主键标识符 CursorResult.inserted_primary_key 我们之前介绍过的存取器。这个 squidwardkrabs 对象现在有了这些新的主键标识符,我们可以通过访问 id 属性:

>>> squidward.id
4
>>> krabs.id
5

小技巧

为什么ORM本可以使用 executemany ? 我们将在下一节中看到 Session 刷新对象时,总是需要知道新插入对象的主键。如果使用诸如SQLite的autoincrement之类的特性(其他示例包括PostgreSQL标识或SERIAL、using sequences等),则 CursorResult.inserted_primary_key 特性通常要求每次插入一行。如果我们提前为主键提供值,ORM将能够更好地优化操作。一些数据库后端,如 psycopg2 还可以一次插入多行,同时仍能检索主键值。

身份图

对象的主键标识对 Session ,因为对象现在使用称为的功能在内存中链接到此标识 identity map . 标识映射是一个内存中的存储,它将当前加载到内存中的所有对象链接到它们的主键标识。我们可以通过使用 Session.get() 方法,如果本地存在,它将从标识映射返回一个条目,否则将发出一个SELECT::

>>> some_squidward = session.get(User, 4)
>>> some_squidward
User(id=4, name='squidward', fullname='Squidward Tentacles')

关于身份映射,需要注意的一点是,它维护一个 唯一实例 每个特定数据库标识的特定Python对象 Session 对象。我们可以观察到 some_squidward 指的是 相同的对象 作为 squidward 以前::

>>> some_squidward is squidward
True

身份映射是一个关键特性,它允许在事务中操作复杂的对象集,而不会使事务变得不同步。

提交

关于 Session 有待进一步讨论的工作。现在,我们将提交事务,以便在检查更多ORM行为和功能之前积累如何选择行的知识:

>>> session.commit()
COMMIT

更新ORM对象

在上一节中 使用核心更新和删除行 ,我们介绍了 Update 表示SQL更新语句的。当使用ORM时,有两种方法使用这个构造。主要的方法是它作为 unit of work 使用的过程 Session ,其中,根据每个主键发出一个UPDATE语句,对应于对其进行更改的各个对象。第二种形式的更新称为“支持ORM的更新”,它允许我们使用 UpdateSession 明确地;这将在下一节中描述。

假设我们装了 User 对象作为用户名 sandy 在交易中(也展示 Select.filter_by() 方法以及 Result.scalar_one() 方法:

sql>>> sandy = session.execute(select(User).filter_by(name="sandy")).scalar_one()
BEGIN (implicit)
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = ?
[...] ('sandy',)

Python对象 sandy 如前所述 代理 对于数据库中的行,更具体地说是数据库行 就目前的交易而言 ,其主键标识为 2 ::

>>> sandy
User(id=2, name='sandy', fullname='Sandy Cheeks')

如果我们改变这个对象的属性 Session 跟踪此更改:

>>> sandy.fullname = "Sandy Squirrel"

对象出现在名为 Session.dirty ,表示对象“脏”:

>>> sandy in session.dirty
True

Session 接下来发出一个刷新,将发出一个更新来更新数据库中的这个值。如前所述,在发出任何SELECT之前,刷新会自动发生,使用一种称为 自动冲洗 . 我们可以直接查询 User.fullname 列,我们将返回更新后的值:

>>> sandy_fullname = session.execute(
...     select(User.fullname).where(User.id == 2)
... ).scalar_one()
UPDATE user_account SET fullname=? WHERE user_account.id = ?
[...] ('Sandy Squirrel', 2)
SELECT user_account.fullname
FROM user_account
WHERE user_account.id = ?
[...] (2,)
>>> print(sandy_fullname)
Sandy Squirrel

我们可以看到上面我们要求 Session 执行单个 select() 声明。然而,发出的SQL显示也发出了一个更新,这是将挂起的更改推出来的刷新进程。这个 sandy Python对象现在不再被认为是脏的::

>>> sandy in session.dirty
False

但请注意我们 仍在交易中 我们的更改还没有被推送到数据库的永久存储中。由于Sandy的姓实际上是“checks”而不是“Squirrel”,我们将在稍后回滚转换时修复此错误。但首先我们要做更多的数据更改。

参见

冲洗 -详细说明刷新过程以及有关 Session.autoflush 设置。

启用ORM的更新语句

如前所述,还有第二种方法根据ORM发出UPDATE语句,这就是 启用ORM的更新语句 . 这允许使用一个可以同时影响多行的通用SQL UPDATE语句。例如,发出将更改 User.fullname 基于列中的值 User.name 专栏:

>>> session.execute(
...     update(User).
...     where(User.name == "sandy").
...     values(fullname="Sandy Squirrel Extraordinaire")
... )
UPDATE user_account SET fullname=? WHERE user_account.name = ?
[...] ('Sandy Squirrel Extraordinaire', 'sandy')
<sqlalchemy.engine.cursor.CursorResult object ...>

当调用启用ORM的UPDATE语句时,将使用特殊逻辑来定位当前会话中与给定条件匹配的对象,以便使用新数据刷新这些对象。上面,是 sandy 对象标识位于内存中并已刷新:

>>> sandy.fullname
'Sandy Squirrel Extraordinaire'

刷新逻辑称为 synchronize_session 选项,和将在本节中详细描述 使用任意WHERE子句更新和删除 .

参见

使用任意WHERE子句更新和删除 -描述ORM使用 update()delete() 以及ORM同步选项。

删除ORM对象

为了完成基本的持久性操作,可以使用 Session.delete() 方法。我们来装吧 patrick 从数据库:

sql>>> patrick = session.get(User, 3)
SELECT user_account.id AS user_account_id, user_account.name AS user_account_name,
user_account.fullname AS user_account_fullname
FROM user_account
WHERE user_account.id = ?
[...] (3,)

如果我们做了记号 patrick 对于删除,与其他操作一样,在刷新继续之前,实际上什么也不会发生:

>>> session.delete(patrick)

当前的ORM行为是 patrick 呆在 Session 直到刷新继续,如前面所述,如果我们发出一个查询:

>>> session.execute(select(User).where(User.name == "patrick")).first()
SELECT address.id AS address_id, address.email_address AS address_email_address,
address.user_id AS address_user_id
FROM address
WHERE ? = address.user_id
[...] (3,)
DELETE FROM user_account WHERE user_account.id = ?
[...] (3,)
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = ?
[...] ('patrick',)

在上面,我们要求发出的SELECT前面有一个DELETE,它表示的是挂起的删除 patrick 继续。还有一个 SELECT 反对 address 表,这是由ORM在该表中查找可能与目标行相关的行时提示的;此行为是称为 cascade ,并可以通过允许数据库处理 address 自动地;部分 删除 有所有的细节。

参见

删除 -描述如何调整的行为 Session.delete() 关于如何处理其他表中的相关行。

除此之外, patrick 现在被删除的对象实例不再被视为在 Session ,如控制检查所示:

>>> patrick in session
False

但是就像我们对 sandy 对象,我们在这里所做的每一个更改都是对正在进行的事务的本地更改,如果我们不提交它,它将不会成为永久性的。由于回滚事务目前实际上更有趣,我们将在下一节中进行此操作。

启用ORM的删除语句

与更新操作一样,还有一个支持ORM的DELETE版本,我们可以使用 delete() 构建与 Session.execute() . 它还有一个特点 未过期 对象(请参见 expired )符合给定删除条件的将自动标记为“:term:deletedSession

>>> # refresh the target object for demonstration purposes
>>> # only, not needed for the DELETE
sql>>> squidward = session.get(User, 4)
SELECT user_account.id AS user_account_id, user_account.name AS user_account_name,
user_account.fullname AS user_account_fullname
FROM user_account
WHERE user_account.id = ?
[...] (4,)

>>> session.execute(delete(User).where(User.name == "squidward"))
DELETE FROM user_account WHERE user_account.name = ?
[...] ('squidward',)
<sqlalchemy.engine.cursor.CursorResult object at 0x...>

这个 squidward 身份,就像 patrick ,现在也处于已删除状态。注意,我们必须重新装载 squidward 为了证明这一点;如果对象已过期,则删除操作不会花时间刷新过期对象,而只是为了查看它们是否已被删除:

>>> squidward in session
False

回滚

这个 Session 有一个 Session.rollback() 方法,该方法按预期对正在进行的SQL连接发出回滚。但是,它对当前与 Session ,在前面的示例中,Python对象 sandy . 当我们改变 .fullnamesandy 要读取的对象 "Sandy Squirrel" ,我们要回滚此更改。打电话 Session.rollback() 不仅会回滚事务而且 到期 当前与此关联的所有对象 Session ,其效果是在下次使用称为 lazy loading

>>> session.rollback()
ROLLBACK

为了更仔细地观察“过期”过程,我们可以观察到Python对象 sandy 它的Python中没有状态 __dict__ ,但特殊的SQLAlchemy内部状态对象除外:

>>> sandy.__dict__
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x...>}

这是“:term:expired”状态;再次访问该属性将自动注册新事务并刷新 sandy 对于当前数据库行:

>>> sandy.fullname
BEGIN (implicit)
SELECT user_account.id AS user_account_id, user_account.name AS user_account_name,
user_account.fullname AS user_account_fullname
FROM user_account
WHERE user_account.id = ?
[...] (2,)
'Sandy Cheeks'

我们现在可以看到,完整的数据库行也被填充到 __dict__sandy 对象:

>>> sandy.__dict__  
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x...>,
 'id': 2, 'name': 'sandy', 'fullname': 'Sandy Cheeks'}

对于已删除的对象,当我们之前注意到 patrick 不再在会话中,该对象的标识也将恢复:

>>> patrick in session
True

当然,数据库数据也会再次出现:

sql>>> session.execute(select(User).where(User.name == 'patrick')).scalar_one() is patrick
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = ?
[...] ('patrick',)
True

关闭会话

在上述章节中,我们使用了 Session 对象,也就是说,我们没有使用 with 声明。这很好,但是如果我们这样做的话,我们最好明确地关闭 Session 完成后:

>>> session.close()
ROLLBACK

关闭 Session ,这也是我们在上下文管理器中使用它时发生的情况,它完成了以下任务:

  • releases 连接池的所有连接资源,取消(例如回滚)正在进行的任何事务。

    这意味着,当我们利用会话执行一些只读任务,然后关闭它时,我们不需要显式地调用 Session.rollback() 以确保事务被回滚;连接池处理此事务。

  • 删除 中的所有对象 Session .

    这意味着我们为此加载的所有Python对象 Session ,像 sandypatricksquidward ,现在处于被称为 detached . 特别是,我们将注意到 expired 例如,由于调用 Session.commit() ,现在不起作用,因为它们不包含当前行的状态,并且不再与要刷新的任何数据库事务相关联:

    >>> squidward.name
    Traceback (most recent call last):
      ...
    sqlalchemy.orm.exc.DetachedInstanceError: Instance <User at 0x...> is not bound to a Session; attribute refresh operation cannot proceed

    分离的对象可以与相同的对象重新关联,也可以与新对象重新关联 Session 使用 Session.add() 方法,该方法将重新建立它们与特定数据库行的关系:

    >>> session.add(squidward)
    >>> squidward.name
    BEGIN (implicit)
    SELECT user_account.id AS user_account_id, user_account.name AS user_account_name, user_account.fullname AS user_account_fullname
    FROM user_account
    WHERE user_account.id = ?
    [...] (4,)
    'squidward'

    小技巧

    尽可能避免使用处于分离状态的对象。当 Session 关闭时,也会清除对所有先前附着对象的引用。对于需要分离对象的情况,通常会立即显示web应用程序的已提交对象,其中 Session 在渲染视图之前关闭,请设置 Session.expire_on_commit 旗到 False .

SQLAlchemy 1.4 / 2.0 Tutorial

下一个教程部分: 使用相关对象