Skip to content

Session

Session表示与数据库建立的所有会话,它提供了进行 CRUD 的接口。就像 SQLAlchemy Core 中使用Connection 一样,在 SQLAlchemy ORM 中使用 Session 对象来真正的与数据库通信。

Tips

注意他们都需要 Engine 对象作为底层的连接和连接池

创建表

SQLAlchemy ORM 中对于表的创建同样使用MetaData中的 create_all() 来完成,而 ORM 中的 MetaData 由Mapped Class中的 registry 来体现:

Python
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 上下文管理器:

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() 来回滚事务:

Python
# 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:

Python
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 容器。一个映射对象具有四种状态:

  1. 临时态(transient): 即手动组装一个映射对象,此时该对象还没有添加到 IdentitySet 容器中,通常也意味着不存在于数据库中
  2. 待处理态(pending): 使用 Session.add()Session.add_all([]) 将临时态对象添加到 IdentitySet 容器中就会处于待处理状态,注意到此时依然没有与数据库进行交互
  3. 持久态(persistent): 可以使用 Session.flush() 来将待处理态的对象添加到数据库中(真正的与数据库进行通信),之后写入数据库的数据被称为持久态。当然也可以通过执行 Session.get() 或者 INSERT 来直接获取持久态对象
  4. 脏数据(dirty): 如果修改的持久态的对象,那么该对象就被认为是脏持久态对象,通常可以重新添加到待处理状态来更新数据库
  5. 已删除(Deleted) : 通过 Session.delete() 删除的持久态对象,注意删除操作同样需要 Session.flush() 来真正执行,如果执行成功将移动到已分离对象,而如果是事务回滚则会移回持久态对象
  6. 已分离(Detached): 之前位于数据库中的对象被成功删除后就处于已分离状态,他表示该对象与数据库不再有联系了

Tips

Session 对象的几种状态以及状态的管理非常类似于 git

获取对象状态

可以使用 inspect() 方法来随时参看映射对象的状态,他返回一个 InstanceState 对象该对象具有五个属性,这五个属性的返回值都是布尔类型指示是否为对应的状态:

  • InstanceState.transient: 是否为临时态
  • InstanceState.pending: 是否为待处理状态
  • InstanceState.persistent: 是否为持久态
  • InstanceState.deleted: 是否已删除
  • InstanceState.detached: 是否已经分离
Python
from sqlalchemy import inspect
insp = inspect(my_object)
insp.persistent
True

更改状态

状态更改的流程,非常类似于 git 的操作方式:

Bash
+-------------+          +-------------+          +-------------+          +-------------+         +-------------+
|             |          |             |          |             |          |             |         |             |
|  transient  |          |   pending   |          |  persistent |          |   deleted   |         |  detached   |
|             |          |             |          |             |          |             |         |             |
+------+------+          +------+------+          +------+------+          +-------+-----+         +------+------+
       |                        |                        |                         |                      |
       |                        |                        |                         |                      |
       |                        |                        |                         |                      |
       |    Session.add         |     Session.flush      |       Session.delete    |    Session.flush     |
       +----------------------> +----------------------> +-----------------------> +--------------------> |
       |                        |                        |                         |                      |
       |                        |                        |                         |                      |
       |    Session.expunge     |                        |                  Session.expunge               |
       | <----------------------+                        +-------------------------+------------------->  |
       |                        |                        |                         |                      |
       |                        |                        |                         |                      |

还有一个特殊的方法 Session.merge() 用于合并:

Python
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 对象作为会话对象他内部维护了一个容器来存储会话中的状态,可以使用迭代器接口来访问所有存在的映射对象:

Python
for obj in session:
    print(obj)

可以使用 inspect() 来查看对象的状态,不过更常用的是使用 Session 的属性来返回一个列表:

Python
# pending 状态的对象
session.new

# dirty 映射对象
session.dirty

# deleted 状态对象
session.deleted

# persistent 状态对象(返回类字典)
session.identity_map

Session的使用流程

首先基于映射类来构造映射对象即表中的行:

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

上面并没有指定 id 这个自增主键,他们会自动在插入是被添加。当我们通过类构造了一个表中的行后并没有与数据库进行交互,此时这些对象处于临时态。我们可以使用 session.add()session.add_all() 来添加临时态的对象,一旦添加就处于待处理态:

Python
session.add(squidward)
session.add(krabs)

session.add_all([squidward, krabs])

可以通过 session.new 属性来查看当前被标记为待处理态的对象:

Python
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(),使用上下文管理是最好的方式:

Python
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])

一旦成功插入到数据库中后,squidwardkrabs 这两个对象将处于持久态。他们会与加载他们的 Session 对象相关联,并且一些属性会更新与数据库同步(例如自增主键):

Python
squidward.id
# 4
krabs.id
# 5

主键的更新在 Session 中非常的重要,因为很多操作都是通过操作这个持久态对象来完成的,而主键这是对象与数据库进行映射的最重要的字段:

  1. 当我们使用基于主键的查询例如 session.get(User, 1) 或者 SELECT 语句来直接从数据库中获取持久态对象
  2. 一个持久态的对象可以使用 session.delete() 来标记为已删除态注意此时并没有删除,只是一个状态标记
  3. 显式执行 session.commit() 后才会真正的删除。真正删除的已删除态对象将被标记为已分离态
  4. 如果修改了持久态对象,那么该对象将被标记为脏对象,该对象将会在下一次 session.commit() 操作后更新数据库中的对应条目后重新变为持久态
  5. 一个脏对象,有时会发现不在希望他们被更改了,此时可以使用 session.expire(u1) 来与数据库通信重新拉取对象来填充它,此时该对象会从脏对象转移至持久态
  6. 如果我们仅仅需要获取持久态对象,并不想修改或者删除他们。可以直接在使用完成后执行 session.expunge(obj) 来直接将持久态对象标记为已分离态
  7. 最后当我们使用完 Session 后需要调用 Session.close() 方法来删除所有 ORM 对象,并释放 Connection 到 Pool 中。当然最好使用 with 上下文管理来自动调用Session.close()

Session 和 Connection

实际上他们两者非常类似:

Python
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:

具体的使用流程:

Python
# 提交
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 对象进行操作:

Python
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)

如果仅仅想要单个会话设置:

Python
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
    ...

更甚至如果想要某一个事务设置:

Python
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.