国家管理¶

Quickie对象状态简介

了解实例在会话中的状态是很有帮助的:

要深入了解所有可能的状态转换,请参阅一节 对象生命周期事件 它描述了每个转换以及如何以编程方式跟踪每个转换。

获取对象的当前状态

任何映射对象的实际状态都可以使用 inspect() 系统:

>>> from sqlalchemy import inspect
>>> insp = inspect(my_object)
>>> insp.persistent
True

会话属性

这个 Session 它的行为有点像一个集合。可以使用迭代器接口访问所有存在的项::

for obj in session:
    print(obj)

并且可以使用常规的“包含”语义来测试存在性:

if obj in session:
    print("Object is present")

会话还跟踪所有新创建的(即挂起的)对象、自上次加载或保存后发生更改的所有对象(即"dirty")以及标记为已删除的所有内容:

# pending objects recently added to the Session
session.new

# persistent objects which currently have changes detected
# (this collection is now created on the fly each time the property is called)
session.dirty

# persistent objects that have been marked as deleted via session.delete(obj)
session.deleted

# dictionary of all persistent objects, keyed on their
# identity key
session.identity_map

(文件: Session.newSession.dirtySession.deletedSession.identity_map

会话引用行为

会话中的对象是 弱引用 . 这意味着当它们在外部应用程序中被取消引用时,它们超出了 Session 以及,由Python解释器进行垃圾收集。这方面的例外包括挂起的对象、标记为已删除的对象或具有挂起更改的持久对象。完全刷新之后,这些集合都是空的,所有对象都再次被弱引用。

使对象 Session 为了保持强引用,通常只需要一个简单的方法。外部管理的强引用行为的示例包括将对象加载到键控其主键的本地字典中,或加载到列表或集合中,以满足它们需要保持引用的时间跨度。这些集合可以与 Session 如果需要,将它们放入 Session.info 字典。

基于事件的方法也是可行的。一个简单的方法,当所有对象保持在 persistent 状态如下:

from sqlalchemy import event

def strong_reference_session(session):
    @event.listens_for(session, "pending_to_persistent")
    @event.listens_for(session, "deleted_to_persistent")
    @event.listens_for(session, "detached_to_persistent")
    @event.listens_for(session, "loaded_as_persistent")
    def strong_ref_object(sess, instance):
        if 'refs' not in sess.info:
            sess.info['refs'] = refs = set()
        else:
            refs = sess.info['refs']

        refs.add(instance)


    @event.listens_for(session, "persistent_to_detached")
    @event.listens_for(session, "persistent_to_deleted")
    @event.listens_for(session, "persistent_to_transient")
    def deref_object(sess, instance):
        sess.info['refs'].discard(instance)

上面,我们截取了 SessionEvents.pending_to_persistent()SessionEvents.detached_to_persistent()SessionEvents.deleted_to_persistent()SessionEvents.loaded_as_persistent() 事件挂钩,以便在对象进入 persistent 过渡,以及 SessionEvents.persistent_to_detached()SessionEvents.persistent_to_deleted() 当对象离开持久状态时,钩住对象。

上述函数可用于 Session 为了在Per上提供强大的引用行为 -Session 依据:

from sqlalchemy.orm import Session

my_session = Session()
strong_reference_session(my_session)

也可以要求任何 sessionmaker ::

from sqlalchemy.orm import sessionmaker

maker = sessionmaker()
strong_reference_session(maker)

合并

merge() 将状态从外部对象传输到会话中的新实例或现有实例。它还将传入的数据与数据库的状态进行协调,生成一个将应用于下一次刷新的历史流,或者也可以在不生成更改历史或访问数据库的情况下生成一个简单的状态“传输”。用法如下:

merged_object = session.merge(existing_object)

当给定一个实例时,它遵循以下步骤:

merge() ,给定的“源”实例既没有修改,也没有与目标关联。 Session ,并可与其他任何数量的 Session 对象。 merge() 用于获取任何类型对象结构的状态,而不考虑其来源或当前会话关联,并将其状态复制到新会话中。以下是一些例子:

合并提示

merge() 是一种非常有用的方法。然而,它处理的是瞬变/分离对象与持久对象之间的复杂边界,以及状态的自动转移。在这里,各种各样的场景都需要更仔细地了解对象的状态。合并的常见问题通常涉及与要传递给的对象有关的一些意外状态 merge() .

让我们使用用户和地址对象的规范示例:

class User(Base):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    name = Column(String(50), nullable=False)
    addresses = relationship("Address", backref="user")

class Address(Base):
    __tablename__ = 'address'

    id = Column(Integer, primary_key=True)
    email_address = Column(String(50), nullable=False)
    user_id = Column(Integer, ForeignKey('user.id'), nullable=False)

假设A User 用一个对象 Address ,已持续::

>>> u1 = User(name='ed', addresses=[Address(email_address='ed@ed.com')])
>>> session.add(u1)
>>> session.commit()

我们现在创造 a1 ,会话外部的一个对象,我们希望将其合并到现有的 Address ::

>>> existing_a1 = u1.addresses[0]
>>> a1 = Address(id=existing_a1.id)

如果我们说:

>>> a1.user = u1
>>> a1 = session.merge(a1)
>>> session.commit()
sqlalchemy.orm.exc.FlushError: New instance <Address at 0x1298f50>
with identity key (<class '__main__.Address'>, (1,)) conflicts with
persistent instance <Address at 0x12a25d0>

为什么?我们对瀑布不小心。转让 a1.user 到级联到的backref的持久对象 User.addresses 使我们 a1 对象挂起,就好像我们添加了它。现在我们有 two Address 会话中的对象:

>>> a1 = Address()
>>> a1.user = u1
>>> a1 in session
True
>>> existing_a1 in session
True
>>> a1 is existing_a1
False

以上,我们 a1 已在会话中挂起。随后 merge() 操作基本上不起作用。层叠可以通过 cascade 选择权 relationship() 尽管在这种情况下,这意味着移除 save-update 层叠自 User.addresses 关系——通常,这种行为非常方便。这里的解决方案通常是不分配 a1.user 目标会话中已持久的对象。

这个 cascade_backrefs=False 选择权 relationship() 也会阻止 Address 通过添加到会话 a1.user = u1 指派。

有关级联操作的更多详细信息,请参见 级联 .

意外状态的另一个示例:

>>> a1 = Address(id=existing_a1.id, user_id=u1.id)
>>> assert a1.user is None
True
>>> a1 = session.merge(a1)
>>> session.commit()
sqlalchemy.exc.IntegrityError: (IntegrityError) address.user_id
may not be NULL

在这里,我们访问了a1.user,它返回了它的默认值 None 由于此访问,已将其放置在 __dict__ 我们的对象 a1 . 通常,此操作不会创建任何更改事件,因此 user_id 在刷新期间,属性优先。但当我们合并 Address 对象进入会话,操作等效于:

>>> existing_a1.id = existing_a1.id
>>> existing_a1.user_id = u1.id
>>> existing_a1.user = None

在上面,两个 user_iduser 分配给,并为两者发出更改事件。这个 user 关联优先,无应用于 user_id ,导致故障。

大多数 merge() 可以通过第一次检查来检查问题-对象是否在会话中过早出现?

>>> a1 = Address(id=existing_a1, user_id=user.id)
>>> assert a1 not in session
>>> a1 = session.merge(a1)

或者对象上有我们不想要的状态?检查 __dict__ 是快速检查的方法:

>>> a1 = Address(id=existing_a1, user_id=user.id)
>>> a1.user
>>> a1.__dict__
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x1298d10>,
    'user_id': 1,
    'id': 1,
    'user': None}
>>> # we don't want user=None merged, remove it
>>> del a1.user
>>> a1 = session.merge(a1)
>>> # success
>>> session.commit()

删去

expunge从会话中删除对象,将持久实例发送到分离状态,并将挂起实例发送到暂时状态:

session.expunge(obj1)

要删除所有项目,请致电 expunge_all() (这种方法以前被称为 clear()

刷新/过期

Expiring 意味着数据库保留在一系列对象属性中的数据将被删除,这样当下次访问这些属性时,将发出一个SQL查询,该查询将从数据库中刷新该数据。

当我们谈论数据到期时,我们通常谈论的是 persistent 状态。例如,如果按如下方式加载对象:

user = session.query(User).filter_by(name='user1').first()

以上 User 对象是持久的,并且存在一系列属性;如果我们要查看它的 __dict__ ,我们将看到加载状态:

>>> user.__dict__
{
  'id': 1, 'name': u'user1',
  '_sa_instance_state': <...>,
}

在哪里? idname 请参阅数据库中的那些列。 _sa_instance_state 是一个非数据库持久化值,由SQLAlchemy在内部使用(它引用 InstanceState 例如。虽然与本节没有直接关系,但是如果我们想了解它,我们应该使用 inspect() 访问它的函数)。

在这一点上,我们的国家 User 对象与加载的数据库行匹配。但在使用诸如 Session.expire() ,我们看到状态被移除:

>>> session.expire(user)
>>> user.__dict__
{'_sa_instance_state': <...>}

我们看到,当内部“状态”仍然存在时,对应于 idname 柱子不见了。如果我们要访问其中一个列并查看SQL,我们将看到:

>>> print(user.name)
SELECT user.id AS user_id, user.name AS user_name FROM user WHERE user.id = ? (1,)
user1

上面,访问过期属性时 user.name ,ORM启动了 lazy load 要从数据库中检索最新状态,请为此用户引用的用户行发出一个选择。之后, __dict__ 再次填充:

>>> user.__dict__
{
  'id': 1, 'name': u'user1',
  '_sa_instance_state': <...>,
}

注解

当我们窥视 __dict__ 为了了解SQLAlchemy对对象属性的作用,我们 不应修改 内容 __dict__ 直接地说,至少在sqlachemy orm维护的那些属性中(sqla领域之外的其他属性都很好)。这是因为SQLAlchemy使用 descriptors 为了跟踪我们对对象所做的更改,以及当我们修改 __dict__ 直接,ORM将无法跟踪我们更改的内容。

两者的另一个关键行为 expire()refresh() 是否放弃对象上所有未刷新的更改。也就是说,如果我们要修改 User ::

>>> user.name = 'user2'

但是我们调用来 expire() 不用先调用 flush() ,我们的待定价值 'user2' 被丢弃:

>>> session.expire(user)
>>> user.name
'user1'

这个 expire() 方法可用于将实例的所有ORM映射属性标记为“已过期”:

# expire all ORM-mapped attributes on obj1
session.expire(obj1)

它还可以传递字符串属性名称列表,引用要标记为过期的特定属性:

# expire only attributes obj1.attr1, obj1.attr2
session.expire(obj1, ['attr1', 'attr2'])

这个 refresh() 方法有一个类似的接口,但它不会过期,而是立即为对象的行发出立即选择:

# reload all attributes on obj1
session.refresh(obj1)

refresh() 还接受字符串属性名称列表,但与 expire() ,应至少有一个名称是列映射属性的名称::

# reload obj1.attr1, obj1.attr2
session.refresh(obj1, ['attr1', 'attr2'])

这个 Session.expire_all() 方法允许我们本质上调用 Session.expire() 在包含在 Session 马上::

session.expire_all()

实际加载的内容

当对象标记为 expire() 或加载 refresh() 取决于几个因素,包括:

  • 过期属性的加载从触发 column-mapped attributes only . 而任何类型的属性都可以标记为过期,包括 relationship() -映射属性,访问过期的 relationship() 属性将只为该属性发出一个加载,使用标准的面向关系的延迟加载。列定向属性,即使过期,也不会作为此操作的一部分加载,而是在访问任何列定向属性时加载。

  • relationship() -将不会加载映射属性以响应正在访问的基于列的过期属性。

  • 关于关系, refresh()expire() 关于未映射列的属性。调用 refresh() 传递仅包含关系映射属性的名称列表实际上会引发错误。在任何情况下,非预加载 relationship() 属性不会包含在任何刷新操作中。

  • relationship() 属性配置为通过 lazy 在以下情况下将加载参数: refresh() ,如果没有指定属性名,或者它们的名称包含在要刷新的属性列表中。

  • 配置为的属性 deferred() 通常不会在过期的属性加载期间或刷新期间加载。卸载的属性 deferred() 相反,当直接访问时,或者如果延迟属性的“组”的一部分(在该组中的已卸载属性被访问时)自行加载。

  • 对于在访问时加载的过期属性,联接继承表映射将发出一个选择,该选择通常只包括那些存在已卸载属性的表。这里的操作非常复杂,可以只加载父表或子表,例如,如果最初过期的列的子集只包含这些表中的一个或另一个表。

  • 什么时候? refresh() 用于联接继承表映射,发出的选择将类似于 Session.query() 用于目标对象的类。这通常是作为映射的一部分设置的所有表。

何时到期或刷新

这个 Session 每当会话引用的事务结束时,自动使用过期功能。意思是,无论何时 Session.commit()Session.rollback() 调用,其中的所有对象 Session 已过期,使用的功能与 Session.expire_all() 方法。理由是,事务的结束是一个分界点,在该分界点上没有更多的上下文可供使用,以了解数据库的当前状态,因为任何数量的其他事务可能会影响数据库。只有当新事务启动时,我们才能再次访问数据库的当前状态,此时可能发生了任何数量的更改。

这个 Session.expire()Session.refresh() 在这些情况下,当需要强制对象从数据库重新加载其数据时,将使用方法;在这些情况下,当已知数据的当前状态可能已过时时,将使用方法。原因可能包括:

  • 某些SQL已在ORM对象处理范围之外的事务中发出,例如 Table.update() 构造是使用 Session.execute() 方法;

  • 如果应用程序试图获取已知在并发事务中已被修改的数据,并且也知道实际的隔离规则允许该数据可见。

第二个要点有一个重要的警告:“众所周知,实际上隔离规则允许此数据可见。”这意味着不能假定在另一个数据库连接上发生的更新在本地仍然可见;在许多情况下,它不会。这就是为什么如果一个人想要使用 expire()refresh() 为了查看正在进行的事务之间的数据,了解实际的隔离行为是至关重要的。

参见

Session.expire()

Session.expire_all()

Session.refresh()

isolation -包括维基百科链接的隔离术语解释。

The SQLAlchemy Session In-Depth -视频+幻灯片,深入讨论对象生命周期,包括数据过期的角色。