在一般的意义上, 会话建立与数据库的所有对话, 并为你在其生命周期中加载或关联的所有对象表示一个“等待区”。他提供了一个入口点获得查询对象, 向数据库发送查询使用会话对象的当前数据库连接, 填充结果行在对象中, 然后存储在会话中, 在这种结构中称为身份映射 – 这种数据结构维护了每一个副本的唯一, 这种唯一意味着一个对象只能有一个特殊的唯一主键。
会话已基本无状态的形式开始。 一旦发出查询或其他对象被持久化, 它就会从一个引擎请求连接资源没, 该引擎要么与会话本身相关联, 要么与正在操作的映射对象相关联。 此连接标识正在进行的事务, 在只是会话提交或回滚其挂起状态之前, 该事务任然有效。
会话中维护的所有变化的对象都会被跟踪 - 在再次查询数据库或提交当前事务之前, 它将刷新对数据库的所有未决更改, 这被称为工作模式单元。
在使用会话时候, 最重要的是要注意与它相关联的对象是会话所持有的事务的代理对象 - 为了保持同步, 有各种各样的时间会导致对象重新访问数据库。 从会话中 分离对象并继续使用他们是可能的。 尽管这种做法尤其局限性。通常, 当你希望再次使用分离的对象时候, 你会将他们与另一个会话重新关联起来, 一遍他们能够恢复表示数据库状态的正常任务。
Session 是一个直接实例化的常规的Python 类。然而, 为了标准会会话的配置和获取方式, sessionmaker 类通常用于创建顶级会话配置, 然后可以在整个应用程序中使用它, 而不是需要重复配置参数。
下面是sessionmaker 的使用方式
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# an Engine, which the Session will use for connection
# resources
some_engine = create_engine('postgresql://scott:tiger@localhost/')
# create a configured "Session" class
Session = sessionmaker(bind=some_engine)
# create a Session
session = Session()
# work with sess
myobject = MyObject('foo', 'bar')
session.add(myobject)
session.commit()
上面sessionmaker 调用为我们创建了一个工厂, 我们将其分配给name会话。 这个工厂在被调用时候, 将还是用我们给工厂的配置参数创建一个新的会话。 在这种情况下, 与通常情况一样, 我们已经将工厂配置为为连接资源指定特定的引擎。
典型的设置是将sessionmaker与引擎关联起来, 这样生成的每个会话将使用这个引擎来获取了;连接资源。 这个关联可以像上面的例子一样, 使用bind参数来建立。
当你编写应用程序时, 请将sessionmaker 工厂放在全局级别。 然后, 应用程序的其余部分可以将此工厂用作新会话实例的源, 并将会话对象如何创造的配置保存在一个位置。
sessionmaker 工厂还可以与其他的助手一起使用, 这些助手通过用户定义的sessionmaker 进行维护。 这些助手中的一些将在“何时构建会话,何时提交会话以及何时关闭会话一节中 讨论。
一个常见的场景是在模块导入时候调用, 但是这与sessionmaker 关联的一个或者多个引擎实例的生成还没有进行。 对于这个用例, sessionmaker 构造提供了sessionmaker.configure()方法, 她将在调用构造时候向现有的sessionmaker 添加额外的配置指令。
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
# configure Session class with desired options
Session = sessionmaker()
# later, we create the engine
engine = create_engine('postgresql://...')
# associate it with our custom Session class
Session.configure(bind=engine)
# work with the session
session = Session()
使用的情况下,应用程序需要创建一个新会话和特殊参数偏离通常是在整个应用程序中使用,如一个会话,绑定到一个备用源的连接,或一个会话,应该有其他参数如expire_on_commit建立不同于大多数应用程序,具体参数可以传递到sessionmaker工厂sessionmaker.call()方法。这些参数将覆盖已经放置的任何配置,例如下面的,其中针对特定连接构造了一个新会话:。
# at the module level, the global sessionmaker,
# bound to a specific Engine
Session = sessionmaker(bind=engine)
# later, some unit of code wants to create a
# Session that is bound to a specific Connection
conn = engine.connect()
session = Session(bind=conn)
将会话与特定连接关联的典型基本原理是维护外部事务的测试夹具—请参阅将会话连接到外部事务(例如测试套件)中的示例。
至此,许多用户已经对会话产生了疑问。本节介绍了一个迷你FAQ(注意,我们也有一个真正的FAQ),介绍了在使用会话时遇到的最基本的问题
只有一次,在应用程序的全局作用域中。它应该被视为应用程序配置的一部分。如果您的应用程序在一个包中有三个.py文件,例如,您可以在init中放置sessionmaker行。py文件;从这一点开始,在其他模块上说“from mypackage import Session”。这样,其他所有人都只使用Session(),而会话的配置由该中心点控制。
如果您的应用程序启动了,进行了导入,但是不知道它将要连接到哪个数据库,那么您可以使用sessionmaker.configure()稍后将“类”级别的会话绑定到引擎。
在本节的示例中,我们将经常在调用会话的行上方显示正在创建的sessionmaker。但这只是一个例子!实际上,sessionmaker可能位于模块级别的某个位置。然后,实例化会话的调用将放在应用程序中数据库会话开始的位置。
通常,会话的生命周期与访问和/或操作数据库数据的函数和对象是分离的,并且是外部的。这将极大地帮助实现可预测的一致事务范围。
确保您清楚事务的开始和结束位置,并保持事务的简短,也就是说,它们以一系列操作结束,而不是无限期地打开。
会话通常是在逻辑操作的开头构造的,在逻辑操作中数据库访问可能是预期的。
每当会话用于与数据库通信时,它在开始通信时就会启动数据库事务。假设autocommit标志的默认值为False,那么在回滚、提交或关闭会话之前,该事务仍在进行中。如果在前一个事务结束之后再次使用新事务,则会话将开始新的事务;由此可见,会话能够跨许多事务拥有一个生命周期,尽管一次只能有一个。我们将这两个概念称为事务作用域和会话作用域。
言外之意是,SQLAlchemy ORM鼓励开发人员在他们的应用程序,建立这两个范围不仅包括范围开始和结束时,还广阔的范围,例如一个会话实例应该本地执行流在一个函数或方法,它应该是整个应用程序使用的全局对象,或在这两个之间。
在SQLAlchemy ORM必须对如何使用数据库有强烈的意见的领域中,开发人员需要承担确定此范围的责任。工作模式的单元是随着时间积累更改并定期刷新更改的单元,它使内存中的状态与本地事务中已知的状态保持同步。这种模式只有在有意义的事务作用域就位时才有效.
通常,确定开始和结束会话范围的最佳时间并不困难,尽管各种各样的应用程序体系结构可能会引入具有挑战性的情况。
一个常见的选择是在事务结束的同时关闭会话,这意味着事务和会话范围是相同的。这是一个很好的选择,因为它消除了将会话作用域与事务作用域分离的需要。
虽然对于如何确定事务范围没有一刀切的建议,但是有一些常见的模式。特别是如果一个人正在编写一个web应用程序,这个选择已经基本确定了。
web应用程序是最简单的情况下,因为这样的应用程序已经围绕一个单一的、一致的范围——这是请求,代表传入请求从浏览器请求制定响应的处理,最后的交付响应返回到客户机。将web应用程序与会话集成是将会话范围链接到请求范围的简单任务。可以在请求开始时建立会话,或者使用延迟初始化模式,在需要时立即建立会话。然后,请求继续进行,应用程序逻辑可以以与实际请求对象的访问方式相关联的方式访问当前会话。当请求结束时,会话也会被删除,通常通过使用web框架提供的事件钩子。会话使用的事务也可以在此时提交,或者应用程序可以选择显式提交模式,只对那些需要提交的请求提交,但仍然总是在最后无条件地销毁会话。
一些web框架包括基础结构,以协助将会话的生命周期与web请求的生命周期进行对齐。这包括Flask- sqlalchemy(与Flask web框架一起使用)和Zope-SQLAlchemy(通常与金字塔框架一起使用)等产品。SQLAlchemy建议尽可能使用这些产品。
在没有提供集成库或集成库不足的情况下,SQLAlchemy包括它自己的“助手”类scoped_session。关于这个对象的用法的教程在上下文/线程本地会话中提供。它既提供了将会话与当前线程关联的快速方法,也提供了将会话对象与其他类型的作用域关联的模式。
正如前面提到的,对于非web应用程序,没有一个明确的模式,因为应用程序本身并不只有一个体系结构模式。最好的策略是尝试划分“操作”,即某个特定线程开始执行一系列操作一段时间的点,这些操作可以在最后提交。一些例子:
通常,应用程序应该将会话的生命周期从外部管理到处理特定数据的函数。这是一种基本的关注点分离,它使特定于数据的操作与它们访问和操作数据的上下文无关。
不要这样做
### this is the **wrong way to do it** ###
class ThingOne(object):
def go(self):
session = Session()
try:
session.query(FooBar).update({"x": 5})
session.commit()
except:
session.rollback()
raise
class ThingTwo(object):
def go(self):
session = Session()
try:
session.query(Widget).update({"q": 18})
session.commit()
except:
session.rollback()
raise
def run_my_program():
ThingOne().go()
ThingTwo().go()
保持会话(通常是事务)的生命周期是独立的和外部的:
### this is a **better** (but not the only) way to do it ###
class ThingOne(object):
def go(self, session):
session.query(FooBar).update({"x": 5})
class ThingTwo(object):
def go(self, session):
session.query(Widget).update({"q": 18})
def run_my_program():
session = Session()
try:
ThingOne().go(session)
ThingTwo().go(session)
session.commit()
except:
session.rollback()
raise
finally:
session.close()
对于更重要的应用程序,建议采用最全面的方法,尽量不让会话、事务和异常管理的细节与执行其工作的程序的细节联系起来。例如,我们可以使用上下文管理器进一步分离关注点:
### another way (but again *not the only way*) to do it ###
from contextlib import contextmanager
@contextmanager
def session_scope():
"""Provide a transactional scope around a series of operations."""
session = Session()
try:
yield session
session.commit()
except:
session.rollback()
raise
finally:
session.close()
def run_my_program():
with session_scope() as session:
ThingOne().go(session)
ThingTwo().go(session)
Yeee…不。它在某种程度上被用作缓存,因为它实现了标识映射模式,并存储了键到主键的对象。但是,它不做任何查询缓存。这意味着,如果您说session.query(Foo).filter_by(name=’bar’),即使Foo(name=’bar’)就在标识映射中,会话对此一无所知。它必须向数据库发出SQL,取回行,然后当它看到该行中的主键时,它就可以在本地标识映射中查看,并看到对象已经在那里了。只有当你说查询的时候。获取会话不需要发出查询的({some primary key})。
此外,会话默认使用弱引用存储对象实例。这也破坏了将会话用作缓存的目的。
会话不是设计成一个全局对象,每个人都将它作为对象的“注册表”进行咨询。这更像是第二级缓存的工作。SQLAlchemy提供了使用dogpile实现二级缓存的模式。缓存,通过Dogpile缓存例子。
使用会话时可用的object_session()类方法:
session = Session.object_session(someobject)
更新的运行时检查API系统也可以使用:
from sqlalchemy import inspect
session = inspect(someobject).session
会话非常希望以非并发的方式使用,这通常意味着每次只在一个线程中使用。
应该以这样一种方式使用会话,即一个实例存在于单个事务中的单个操作系列中。获得这种效果的一种权宜之计是将会话与当前线程关联起来(有关背景信息,请参阅上下文/线程本地会话)。另一种方法是使用一种模式,在这种模式中,会话在函数之间传递,否则不会与其他线程共享。
更重要的一点是,您不应该使用具有多个并发线程的会话。这就像餐馆里的每个人都用同一个盘子吃饭一样。会话是用于一组特定任务的本地“工作区”;您不希望或需要与正在执行其他任务的其他线程共享该会话。
确保每次只在单个并发线程中使用会话被称为并发的“无共享”方法。但实际上,不共享会话意味着一个更重要的模式;它不仅意味着会话对象本身,而且还意味着与该会话关联的所有对象都必须保持在单个并发线程的范围内。与会话关联的映射对象集本质上是通过数据库连接访问的数据库行中的数据的代理,因此就像会话本身一样,整个对象集实际上只是数据库连接(或连接)的大型代理。最终,我们要避免并发访问的主要是DBAPI连接本身;但是,由于会话及其关联的所有对象都是该DBAPI连接的代理,因此对于并发访问来说,整个图本质上是不安全的。
如果实际上有多个线程参与同一任务,那么您可以考虑在这些线程之间共享会话及其对象;但是,在这种非常不寻常的场景中,应用程序需要确保实现了适当的锁定方案,以便不对会话或会话状态进行并发访问。对于这种情况,一种更常见的方法是为每个并发线程维护一个会话,而不是将对象从一个会话复制到另一个会话,通常使用Session.merge()方法将对象的状态复制到一个不同会话的新的本地对象中。
这里介绍了最基本的会话使用模式。
Querying
query()函数接受一个或多个实体,并返回一个新的查询对象,该对象将在此会话的上下文中发出mapper查询。实体被定义为映射类、映射对象、支持擦鞋垫的描述符或AliasedClass对象:
# query from a class
session.query(User).filter_by(name='ed').all()
# query with multiple classes, returns tuples
session.query(User, Address).join('addresses').filter_by(name='ed').all()
# query using orm-enabled descriptors
session.query(User.name, User.fullname).all()
# query from a mapper
user_mapper = class_mapper(User)
session.query(user_mapper)
当查询返回结果时,每个实例化的对象都存储在标识映射中。当一行与已经存在的对象匹配时,将返回相同的对象。在后一种情况下,是否将行填充到现有对象取决于实例的属性是否过期。默认配置的会话会自动过期所有事务边界上的实例,因此,对于通常隔离的事务,不应该存在表示当前事务的过时数据的实例的任何问题。
在对象关系教程中详细介绍了查询对象,并在Query API中进一步介绍。
add()用于在会话中放置实例。对于瞬态(即全新的)实例,这将在下一次刷新时对这些实例执行插入操作。对于持久的实例(即由此会话加载的实例),它们已经存在,不需要添加。分离的实例(即从会话中删除的实例)可以使用以下方法与会话重新关联:
user1 = User(name='user1')
user2 = User(name='user2')
session.add(user1)
session.add(user2)
session.commit() # write changes to the database
要立即向会话添加项目列表,请使用add_all():
session.add_all([item1, item2, item3])
add()操作沿着save-update级联。有关详细信息,请参阅级联部分。
delete()方法在会话的对象列表中放置一个实例,将其标记为已删除:
# mark two objects to be deleted
session.delete(obj1)
session.delete(obj2)
# commit (or flush)
session.commit()
从集合和标量关系中删除引用的对象
ORM通常不会在刷新过程中修改集合或标量关系的内容。这意味着,如果您的类有一个引用对象集合的relationship(),或者引用单个对象(如多对一),则在刷新过程发生时不会修改该属性的内容。相反,如果会话在之后过期,要么通过Session.commit()的过期行为,要么通过显式使用Session.expire(),将清除与该会话关联的给定对象上的引用对象或集合,并在下次访问时重新加载自身。
不要将此行为与刷新过程对引用外键和主键列的列绑定属性的影响混淆;这些属性在flush中可以随意修改,因为flush流程打算管理这些属性。它不应该与反向引用的行为相混淆,正如在链接与Backref的关系时所描述的那样;backreference事件将修改集合或标量属性引用,但是这种行为是在直接操作相关集合和对象引用时发生的,这在调用应用程序中是显式的,在刷新过程之外。
与此行为相关的一个常见混淆涉及到delete()方法的使用。当在对象上调用Session.delete()并刷新会话时,将从数据库中删除该行。通过外键引用目标行的行(假设使用两个映射对象类型之间的relationship()进行跟踪)也将看到它们的外键属性更新为null,或者如果设置了delete cascade,相关的行也将被删除。然而,即使与删除的对象相关的行本身也可能被修改,但在flush范围内,对涉及操作的对象的关系绑定集合或对象引用不会发生任何更改。这意味着如果对象是相关集合的成员,那么它仍然会出现在Python端,直到该集合过期。类似地,如果对象是通过另一个对象的多对一或一对一引用的,则该引用将保持在该对象上,直到对象过期为止。
下面,我们将说明在地址对象被标记为删除后,它仍然存在于与父用户关联的集合中,即使在刷新之后:
>>> address = user.addresses[1]
>>> session.delete(address)
>>> session.flush()
>>> address in user.addresses
True
当提交上述会话时,所有属性都过期了。用户的下一个访问。地址将重新加载集合,显示所需的状态
>>> session.commit()
>>> address in user.addresses
False
有一个方法可以拦截Session.delete()并自动调用这个过期函数;请参见呼气elelationshiponfkchange。但是,在集合中删除项的通常做法是放弃直接使用delete(),而是使用级联行为作为从父集合中删除对象的结果自动调用删除。删除孤儿级联实现了这一点,如下例所示:
class User(Base):
__tablename__ = 'user'
# ...
addresses = relationship(
"Address", cascade="all, delete, delete-orphan")
# ...
del user.addresses[1]
session.flush()
在上面的地方,在从用户中删除Address对象时。地址集合,delete-orphan级联具有将地址对象标记为删除的效果,就像传递给delete()一样。
delete-orphan级联还可以应用于多对一或一对一关系,这样当一个对象从它的父对象中分离时,它也会自动标记为删除。在多对一或一对一上使用删除孤立级联需要额外的标记关系。single_parent调用断言该相关对象不同时与任何其他父类共享:
class User(Base):
# ...
preference = relationship(
"Preference", cascade="all, delete, delete-orphan",
single_parent=True)
上面,如果一个假设的首选项对象从用户中删除,它将在刷新时被删除:
some_user.preference = None
session.flush() # will delete the Preference object
有关级联的详细信息,请参阅级联。
基于筛选条件的删除
对Session.delete()的警告是,为了删除,您需要手边已经有一个对象。该查询包含一个delete()方法,该方法基于过滤条件进行删除:
session.query(User).filter(User.id==7).delete()
delete()方法包括“过期”会话中已经存在的符合条件的对象的功能。然而,它确实有一些警告,包括“删除”和“删除孤儿”级联不会完全表示为已加载的集合。有关delete()的详细信息,请参阅API文档
Flushing
当会话与其默认配置一起使用时,刷新步骤几乎总是透明地完成的。具体来说,刷新发生在发出单个查询之前,以及提交事务之前的commit()调用中。当使用begin_嵌套()方法时,它也发生在发出保存点之前。
不管自动刷新设置如何,刷新总是可以通过发出flush()来强制执行:
session.flush()
不管自动刷新设置如何,总是可以通过发出flush()来强制刷新:通过使用autoflush=False构造sessionmaker,可以禁用行为的“查询上刷新”方面:
Session = sessionmaker(autoflush=False)
另外,通过随时设置自动刷新标志,可以暂时禁用自动刷新:
mysession = Session()
mysession.autoflush = False
在DisableAutoFlush中可以找到一些自动刷新禁用的食谱。
刷新过程总是在事务内部发生,即使会话已经配置了autocommit=True,这个设置会禁用会话的持久事务状态。如果不存在事务,flush()创建自己的事务并提交。刷新期间的任何故障都将导致出现任何事务的回滚。如果会话不是在autocommit=True模式下,那么在刷新失败后需要显式调用rollback(),即使底层事务已经回滚了——这样就可以始终保持所谓的“子事务”的整体嵌套模式。
commit()用于提交当前事务。它总是预先发出flush()以将任何剩余状态刷新到数据库;这与“自动刷新”设置无关。如果不存在事务,则会引发错误。注意,会话的默认行为是始终存在“事务”;可以通过设置autocommit=True来禁用此行为。在自动提交模式中,可以通过调用begin()方法启动事务。
这里的术语“事务”指的是会话本身中的事务构造,它可能维护零个或多个实际数据库(DBAPI)事务。单个DBAPI连接开始参与“事务”,因为它首先用于执行SQL语句,然后保持存在,直到会话级别的“事务”完成。有关更多细节,请参见管理事务。
commit()的另一种行为是,默认情况下,它会在提交完成后终止所有实例的状态。这样,当下一次访问实例时,无论是通过属性访问,还是通过查询结果集中出现的实例,它们都会接收到最近的状态。要禁用此行为,请使用expire_on_commit=False配置sessionmaker。
通常,加载到会话中的实例不会被后续查询更改;假设当前事务是隔离的,因此只要事务继续进行,最近加载的状态就是正确的。设置autocommit=True在一定程度上对这个模型有效,因为会话在属性状态方面的行为完全相同,只是不存在事务。
rollback()回滚当前事务。对于默认配置的会话,会话的后回滚状态如下:
了解了该状态后,会话可以在回滚发生后安全地继续使用。
当flush()失败时(通常由于主键、外键或“不可空”约束违反等原因),就会自动发出rollback()(目前在部分失败后无法继续刷新)。但是,刷新过程总是使用自己的事务标定器,称为子事务,在会话文档字符串中更详细地描述了这一点。这里的意思是,即使数据库事务已经回滚,最终用户仍然必须发出rollback()来完全重置会话的状态。
close()方法发出expunge_all(),并释放任何事务性/连接资源。当连接返回到连接池时,事务状态也会回滚。