Session
Session表示与数据库建立的所有会话,它提供了进行 CRUD 的接口。就像 SQLAlchemy Core
中使用Connection 一样,在 SQLAlchemy ORM
中使用 Session 对象来真正的与数据库通信。
Tips
注意他们都需要 Engine 对象作为底层的连接和连接池
创建表
SQLAlchemy ORM
中对于表的创建同样使用MetaData中的 create_all()
来完成,而 ORM 中的 MetaData 由Mapped Class中的 registry 来体现:
Base.metadata.create_all(engine)
class Base(DeclarativeBase):
pass
Base.metadata.create_all(engine)
# 如果想要实现对某个表的创建
Base.metadata.tables['user'].create(engine)
# 默认情况如果存在创建会失败,可以指定 checkfirst = True 来在创建前进行检查
Base.metadata.tables[User.__table__].create(engine, checkfirst=True)
打开和关闭会话
Session 同样支持 python 上下文管理器:
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
# an Engine, which the Session will use for connection
# resources
engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")
# create session and add objects
with Session(engine) as session:
session.add(some_object) # 映射类实例
session.add(some_other_object)
session.commit()
使用 session.begin()
来启用 BeginOnce 事务以及 session.rollback()
来回滚事务:
# create session and add objects
with Session(engine) as session:
with session.begin():
session.add(some_object)
session.add(some_other_object)
# 等同于
with Session(engine) as session:
session.begin()
try:
session.add(some_object)
session.add(some_other_object)
except:
session.rollback()
raise
else:
session.commit()
也可以使用 sessionmaker(engine)
构造一个 Session 对象,他主要是可以定制不同的 engine:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# an Engine, which the Session will use for connection
# resources
engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")
# a sessionmaker(), also in the same scope as the engine
Session = sessionmaker(engine)
# we can now construct a Session() and include begin()/commit()/rollback()
# at once
with Session.begin() as session:
session.add(some_object)
session.add(some_other_object)
# commits the transaction, closes the session
五种状态
Session 作为会话对象,他内部维护了一个容器,容器中包含 SQLAlchemy ORM
中的映射对象的唯一副本。Session 同样需要 Engine 对象来与数据库进行通信,而通信的源以及结果都需要经过一个 IdentitySet 容器。一个映射对象具有四种状态:
- 临时态(transient): 即手动组装一个映射对象,此时该对象还没有添加到 IdentitySet 容器中,通常也意味着不存在于数据库中
- 待处理态(pending): 使用
Session.add()
或Session.add_all([])
将临时态对象添加到 IdentitySet 容器中就会处于待处理状态,注意到此时依然没有与数据库进行交互 - 持久态(persistent): 可以使用
Session.flush()
来将待处理态的对象添加到数据库中(真正的与数据库进行通信),之后写入数据库的数据被称为持久态。当然也可以通过执行Session.get()
或者 INSERT 来直接获取持久态对象 - 脏数据(dirty): 如果修改的持久态的对象,那么该对象就被认为是脏持久态对象,通常可以重新添加到待处理状态来更新数据库
- 已删除(Deleted) : 通过
Session.delete()
删除的持久态对象,注意删除操作同样需要Session.flush()
来真正执行,如果执行成功将移动到已分离对象,而如果是事务回滚则会移回持久态对象 - 已分离(Detached): 之前位于数据库中的对象被成功删除后就处于已分离状态,他表示该对象与数据库不再有联系了
Tips
Session 对象的几种状态以及状态的管理非常类似于 git
获取对象状态
可以使用 inspect()
方法来随时参看映射对象的状态,他返回一个 InstanceState 对象该对象具有五个属性,这五个属性的返回值都是布尔类型指示是否为对应的状态:
InstanceState.transient
: 是否为临时态InstanceState.pending
: 是否为待处理状态InstanceState.persistent
: 是否为持久态InstanceState.deleted
: 是否已删除InstanceState.detached
: 是否已经分离
更改状态
状态更改的流程,非常类似于 git 的操作方式:
+-------------+ +-------------+ +-------------+ +-------------+ +-------------+
| | | | | | | | | |
| transient | | pending | | persistent | | deleted | | detached |
| | | | | | | | | |
+------+------+ +------+------+ +------+------+ +-------+-----+ +------+------+
| | | | |
| | | | |
| | | | |
| Session.add | Session.flush | Session.delete | Session.flush |
+----------------------> +----------------------> +-----------------------> +--------------------> |
| | | | |
| | | | |
| Session.expunge | | Session.expunge |
| <----------------------+ +-------------------------+-------------------> |
| | | | |
| | | | |
还有一个特殊的方法 Session.merge()
用于合并:
up1 = User(id=1, name='name1', fullname= 'fullname1')
inspect(up1).transient # True 即创建的新对象为临时态
# 但是我们指定了id即有主键,要想将他添加到 Session 中可使用
# 这有几种情况
# 1. 如果 session 的持久状态对象中包含同样的对象则默认无操作
# 2. 如果不存在,则执行 session.get(User, 1) 来获取一个持久态对象(注意并不是up1)
# 3. 如果数据库中不存在 id=1 的 User,则添加一个新对象相当于 session.add(up1)
session.merge(up1)
Session 属性
Session 对象作为会话对象他内部维护了一个容器来存储会话中的状态,可以使用迭代器接口来访问所有存在的映射对象:
可以使用 inspect()
来查看对象的状态,不过更常用的是使用 Session 的属性来返回一个列表:
# pending 状态的对象
session.new
# dirty 映射对象
session.dirty
# deleted 状态对象
session.deleted
# persistent 状态对象(返回类字典)
session.identity_map
Session的使用流程
首先基于映射类来构造映射对象即表中的行:
squidward = User(name="squidward", fullname="Squidward Tentacles")
krabs = User(name="ehkrabs", fullname="Eugene H. Krabs")
上面并没有指定 id 这个自增主键,他们会自动在插入是被添加。当我们通过类构造了一个表中的行后并没有与数据库进行交互,此时这些对象处于临时态。我们可以使用 session.add()
或 session.add_all()
来添加临时态的对象,一旦添加就处于待处理态:
可以通过 session.new
属性来查看当前被标记为待处理态的对象:
session.new
# IdentitySet([User(id=None, name='squidward', fullname='Squidward Tentacles'), User(id=None, name='ehkrabs', fullname='Eugene H. Krabs')])
而要想真正将数据写入通常需要执行 session.flush()
,他表示我们手动将待处理的更改推送到当前事务(注意并不是推送到数据库)上,之后只要手动执行 session.commit()
来提交事务就是真正的写入了,不过通常不需要我们手动执行 session.flush()
,使用上下文管理是最好的方式:
with Session(engine) as session:
session.add_all([squidward, krabs])
session.commit()
# 更可以
Session = sessionmaker(engine)
# we can now construct a Session() and include begin()/commit()/rollback()
# at once
with Session.begin() as session:
session.add_all([squidward, krabs])
一旦成功插入到数据库中后,squidward
和 krabs
这两个对象将处于持久态。他们会与加载他们的 Session 对象相关联,并且一些属性会更新与数据库同步(例如自增主键):
主键的更新在 Session 中非常的重要,因为很多操作都是通过操作这个持久态对象来完成的,而主键这是对象与数据库进行映射的最重要的字段:
- 当我们使用基于主键的查询例如
session.get(User, 1)
或者 SELECT 语句来直接从数据库中获取持久态对象 - 一个持久态的对象可以使用
session.delete()
来标记为已删除态。注意此时并没有删除,只是一个状态标记 - 显式执行
session.commit()
后才会真正的删除。真正删除的已删除态对象将被标记为已分离态 - 如果修改了持久态对象,那么该对象将被标记为脏对象,该对象将会在下一次
session.commit()
操作后更新数据库中的对应条目后重新变为持久态 - 一个脏对象,有时会发现不在希望他们被更改了,此时可以使用
session.expire(u1)
来与数据库通信重新拉取对象来填充它,此时该对象会从脏对象转移至持久态。 - 如果我们仅仅需要获取持久态对象,并不想修改或者删除他们。可以直接在使用完成后执行
session.expunge(obj)
来直接将持久态对象标记为已分离态 - 最后当我们使用完 Session 后需要调用
Session.close()
方法来删除所有 ORM 对象,并释放 Connection 到 Pool 中。当然最好使用 with 上下文管理来自动调用Session.close()
Session 和 Connection
实际上他们两者非常类似:
ORM Core
----------------------------------------- -----------------------------------
sessionmaker Engine
Session Connection
sessionmaker.begin() Engine.begin()
some_session.commit() some_connection.commit()
with some_sessionmaker() as session: with some_engine.connect() as conn:
with some_sessionmaker.begin() as session: with some_engine.begin() as conn:
with some_session.begin_nested() as sp: with some_connection.begin_nested() as sp:
具体的使用流程:
# 提交
engine = create_engine("postgresql+psycopg2://user:pass@host/dbname")
with engine.connect() as conn:
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
conn.commit()
Session = sessionmaker(engine)
with Session() as session:
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
session.commit()
就像上面的提交,区别仅仅在与一个是字典,一个是映射类实例。后者可以很好的利用 IDE 以及类型提示。
Tips
对于大量的插入操作,构造映射对象本身可能没有意义,session 同样可以使用 execute 方法来接受一个 insert 语句来实现插入,此时与 connection 对象基本一致。甚至你可以通过 session.connection
来返回连接对象
Session 对象本身并不参与与数据库之间的交互,其底层依然使用 Connection 对象来实现交互。这意味着像定义事务隔离级别以及 stream_results 流式传输同样需要对底层的 Connection 对象进行操作:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# 可以直接绑定到 Engine 对象上
eng = create_engine(
"postgresql+psycopg2://scott:tiger@localhost/test",
isolation_level="REPEATABLE READ",
)
# 上下两者等价
eng = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")
autocommit_engine = eng.execution_options(isolation_level="AUTOCOMMIT")
Session = sessionmaker(eng)
如果仅仅想要单个会话设置:
plain_engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")
autocommit_engine = plain_engine.execution_options(isolation_level="AUTOCOMMIT")
# will normally use plain_engine
Session = sessionmaker(plain_engine)
# make a specific Session that will use the "autocommit" engine
with Session(bind=autocommit_engine) as session:
# work with session
...
更甚至如果想要某一个事务设置:
sess = Session(bind=engine)
with sess.begin():
# call connection() with options before any other operations proceed.
# this will procure a new connection from the bound engine and begin a
# real database transaction.
sess.connection(execution_options={"isolation_level": "SERIALIZABLE"})
# ... work with session in SERIALIZABLE isolation level...
# outside the block, the transaction has been committed. the connection is
# released and reverted to its previous isolation level.