SqlAlchemy 2.0 中文文档(八十一)(3)

简介: SqlAlchemy 2.0 中文文档(八十一)

SqlAlchemy 2.0 中文文档(八十一)(2)https://developer.aliyun.com/article/1559903


从外部表/子查询映射关系

这个功能在 0.3 版本中悄然出现,但在 0.4 版本中得到改进,这要归功于更好地将针对表的子查询转换为针对该表的别名的能力;这对于急加载、查询中的别名连接等非常重要。这减少了在只需要添加一些额外列或子查询时对选择语句创建映射器的需求:

mapper(
    User,
    users,
    properties={
        "fullname": column_property(
            (users.c.firstname + users.c.lastname).label("fullname")
        ),
        "numposts": column_property(
            select([func.count(1)], users.c.id == posts.c.user_id)
            .correlate(users)
            .label("posts")
        ),
    },
)

典型的查询如下:

SELECT  (SELECT  count(1)  FROM  posts  WHERE  users.id  =  posts.user_id)  AS  count,
users.firstname  ||  users.lastname  AS  fullname,
users.id  AS  users_id,  users.firstname  AS  users_firstname,  users.lastname  AS  users_lastname
FROM  users  ORDER  BY  users.oid

水平扩展(分片)API

[browser:/sqlalchemy/trunk/examples/sharding/attribute_shard .py]

会话

新的会话创建范式;SessionContext,assignmapper 已弃用

是的,整个事情都被两个配置函数替换了。同时使用两者将产生自 0.1 版本以来最接近 0.1 版本的感觉(即,输入最少)。

在定义引擎(或任何地方)的地方配置您自己的 Session 类��

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine("myengine://")
Session = sessionmaker(bind=engine, autoflush=True, transactional=True)
# use the new Session() freely
sess = Session()
sess.save(someobject)
sess.flush()

如果您需要在之后使用 configure() 来后置配置您的会话,比如添加引擎:

Session.configure(bind=create_engine(...))

SessionContext 的所有行为以及 assignmapperquery__init__ 方法都移动到了新的 scoped_session() 函数中,该函数与 sessionmakercreate_session() 兼容:

from sqlalchemy.orm import scoped_session, sessionmaker
Session = scoped_session(sessionmaker(autoflush=True, transactional=True))
Session.configure(bind=engine)
u = User(name="wendy")
sess = Session()
sess.save(u)
sess.commit()
# Session constructor is thread-locally scoped.  Everyone gets the same
# Session in the thread when scope="thread".
sess2 = Session()
assert sess is sess2

当使用线程本地的 Session 时,返回的类已经实现了所有 Session 的接口作为类方法,并且可以使用 mapper 类方法来使用 “assignmapper” 的功能。就像旧的 objectstore 时代一样……。

# "assignmapper"-like functionality available via ScopedSession.mapper
Session.mapper(User, users_table)
u = User(name="wendy")
Session.commit()
会话再次默认为弱引用

weak_identity_map 标志现在默认设置为 True 在 Session 上。外部解除引用并且超出范围的实例会自动从会话中移除。但是,具有“脏”更改的项目将保持强引用,直到这些更改被刷新,此时对象将恢复为弱引用(这适用于像可选属性这样的“可变”类型)。将 weak_identity_map 设置为 False 将为那些像缓存一样使用会话的人恢复旧的强引用行为。

自动事务会话

正如您在上面所注意到的,我们在 Session 上调用 commit()。标志 transactional=True 意味着 Session 总是处于事务中,commit() 永久持久化。

自动刷新会话

此外,autoflush=True 意味着 Session 将在每次 query 之前刷新,以及在调用 flush()commit() 时。所以现在这将起作用:

Session = sessionmaker(bind=engine, autoflush=True, transactional=True)
u = User(name="wendy")
sess = Session()
sess.save(u)
# wendy is flushed, comes right back from a query
wendy = sess.query(User).filter_by(name="wendy").one()
事务方法移动到会话上

commit()rollback(),以及 begin() 现在直接在 Session 上。不再需要为任何事情使用 SessionTransaction(它仍然在后台运行)。

Session = sessionmaker(autoflush=True, transactional=False)
sess = Session()
sess.begin()
# use the session
sess.commit()  # commit transaction

与封闭的引擎级(即非 ORM)事务共享 Session 很容易:

Session = sessionmaker(autoflush=True, transactional=False)
conn = engine.connect()
trans = conn.begin()
sess = Session(bind=conn)
# ... session is transactional
# commit the outermost transaction
trans.commit()
使用 SAVEPOINT 的嵌套会话事务

在引擎和 ORM 级别可用。迄今为止的 ORM 文档:

www.sqlalchemy.org/docs/04/session.html#unitofwork_managing

两阶段提交会话

在引擎和 ORM 级别可用。迄今为止的 ORM 文档:

www.sqlalchemy.org/docs/04/session.html#unitofwork_managing

继承

无联接或联合的多态继承

继承的新文档:www.sqlalchemy.org/docs/04 /mappers.html#advdatamapping_mapper_inheritance_joined

使用get()实现更好的多态行为

在联接表继承层次结构中,所有类都使用基类获得_instance_key,即(BaseClass, (1, ), None)。这样,当您针对基类调用get()时,它可以在当前标识映射中定位子类实例,而无需查询数据库。

类型

自定义sqlalchemy.types.TypeDecorator的子类

有一个新的 API用于子类化 TypeDecorator。在某些情况下,使用 0.3 API 会导致编译错误。

查询

新的查询 API

查询标准化为生成式接口(旧接口仍然存在,只是已弃用)。虽然大部分生成式接口在 0.3 中可用,但 0.4 查询具有与生成式外部匹配的内部实现,并且有更多技巧。所有结果缩小都通过filter()filter_by()进行,限制/偏移要么通过数组切片要么通过limit()/offset()进行,连接通过join()outerjoin()进行(或更手动地,通过select_from()以及手动形成的条件)。

为避免弃用警告,您必须对您的 03 代码进行一些更改

User.query.get_by( **kwargs )

User.query.filter_by(**kwargs).first()

User.query.select_by( **kwargs )

User.query.filter_by(**kwargs).all()

User.query.select()

User.query.filter(xxx).all()
新的基于属性的表达式构造

在 ORM 中最明显的区别是,现在你可以直接使用基于类的属性构建查询条件。在使用映射类时不再需要“.c.”前缀:

session.query(User).filter(and_(User.name == "fred", User.id > 17))

虽然简单的基于列的比较不是什么大问题,但类属性有一些新的“更高级”的构造可用,包括以前仅在filter_by()中可用的内容:

# comparison of scalar relations to an instance
filter(Address.user == user)
# return all users who contain a particular address
filter(User.addresses.contains(address))
# return all users who *dont* contain the address
filter(~User.address.contains(address))
# return all users who contain a particular address with
# the email_address like '%foo%'
filter(User.addresses.any(Address.email_address.like("%foo%")))
# same, email address equals 'foo@bar.com'.  can fall back to keyword
# args for simple comparisons
filter(User.addresses.any(email_address="foo@bar.com"))
# return all Addresses whose user attribute has the username 'ed'
filter(Address.user.has(name="ed"))
# return all Addresses whose user attribute has the username 'ed'
# and an id > 5 (mixing clauses with kwargs)
filter(Address.user.has(User.id > 5, name="ed"))

Column集合仍然可以在映射类的.c属性中使用。请注意,基于属性的表达式仅适用于映射类的映射属性。在正常表和从 SQL 表达式生成的可选择对象中,仍然使用.c来访问列。

自动连接别名

我们现在已经有了 join()和 outerjoin():

session.query(Order).join("items")

现在你可以给它们起别名:

session.query(Order).join("items", aliased=True).filter(Item.name="item 1").join(
    "items", aliased=True
).filter(Item.name == "item 3")

上述将从 orders->items 创建两个连接,使用别名。每个连接后续的filter()调用将调整其表条件为别名的条件。要获取Item对象,请使用add_entity()并将每个连接的id作为目标:

session.query(Order).join("items", id="j1", aliased=True).filter(
    Item.name == "item 1"
).join("items", aliased=True, id="j2").filter(Item.name == "item 3").add_entity(
    Item, id="j1"
).add_entity(
    Item, id="j2"
)

返回形式为的元组:(Order,Item,Item)

自引用查询

因此,query.join()现在可以创建别名。那给了我们什么?自引用查询!连接可以在没有任何Alias对象的情况下完成:

# standard self-referential TreeNode mapper with backref
mapper(
    TreeNode,
    tree_nodes,
    properties={
        "children": relation(
            TreeNode, backref=backref("parent", remote_side=tree_nodes.id)
        )
    },
)
# query for node with child containing "bar" two levels deep
session.query(TreeNode).join(["children", "children"], aliased=True).filter_by(
    name="bar"
)

要为沿途的每个表添加条件以进行别名连接,您可以使用from_joinpoint来继续针对相同行别名进行连接:

# search for the treenode along the path "n1/n12/n122"
# first find a Node with name="n122"
q = sess.query(Node).filter_by(name="n122")
# then join to parent with "n12"
q = q.join("parent", aliased=True).filter_by(name="n12")
# join again to the next parent with 'n1'.  use 'from_joinpoint'
# so we join from the previous point, instead of joining off the
# root table
q = q.join("parent", aliased=True, from_joinpoint=True).filter_by(name="n1")
node = q.first()
query.populate_existing()

query.load()(或session.refresh())的急切版本。如果查询中加载的每个实例,包括所有急切加载的项,已经存在于会话中,则立即刷新它们:

session.query(Blah).populate_existing().all()
新的查询 API

查询标准化为生成接口(旧接口仍在,只是已弃用)。虽然大多数生成接口在 0.3 中可用,但 0.4 版本的查询具有匹配生成外部的内部要领,并且有更多技巧。所有结果缩小都通过filter()filter_by()进行,限制/偏移要么通过数组切片,要么通过limit()/offset()进行,连接是通过join()outerjoin()进行的(或者更手动地,通过select_from()以及手动形成的条件)。

为了避免弃用警告,您必须对您的 03 代码进行一些更改

User.query.get_by(**kwargs)

User.query.filter_by(**kwargs).first()

User.query.select_by(**kwargs)

User.query.filter_by(**kwargs).all()

User.query.select()

User.query.filter(xxx).all()
新的基于属性的表达式构造

在 ORM 中最明显的区别是,现在您可以直接使用基于类的属性构造查询条件。当使用映射类时,不再需要“ .c.”前缀:

session.query(User).filter(and_(User.name == "fred", User.id > 17))

虽然简单的基于列的比较不是什么大不了的事,但类属性具有一些新的“更高级别”的构造可用,包括以前仅在filter_by()中可用的内容:

# comparison of scalar relations to an instance
filter(Address.user == user)
# return all users who contain a particular address
filter(User.addresses.contains(address))
# return all users who *dont* contain the address
filter(~User.address.contains(address))
# return all users who contain a particular address with
# the email_address like '%foo%'
filter(User.addresses.any(Address.email_address.like("%foo%")))
# same, email address equals 'foo@bar.com'.  can fall back to keyword
# args for simple comparisons
filter(User.addresses.any(email_address="foo@bar.com"))
# return all Addresses whose user attribute has the username 'ed'
filter(Address.user.has(name="ed"))
# return all Addresses whose user attribute has the username 'ed'
# and an id > 5 (mixing clauses with kwargs)
filter(Address.user.has(User.id > 5, name="ed"))

.c属性上的Column集合仍然可用于映射类中。请注意,基于属性的表达式仅适用于映射类的映射属性。.c仍然用于访问常规表中的列以及从 SQL 表达式生成的可选择对象。

自动连接别名

我们已经有了一段时间的 join()和 outerjoin():

session.query(Order).join("items")

现在你可以给它们取别名:

session.query(Order).join("items", aliased=True).filter(Item.name="item 1").join(
    "items", aliased=True
).filter(Item.name == "item 3")

以上将使用别名从订单->项目创建两个连接。每个之后的filter()调用将其表条件调整为别名的条件。要访问Item对象,请使用add_entity()并将每个连接目标化为id

session.query(Order).join("items", id="j1", aliased=True).filter(
    Item.name == "item 1"
).join("items", aliased=True, id="j2").filter(Item.name == "item 3").add_entity(
    Item, id="j1"
).add_entity(
    Item, id="j2"
)

返回形式为的元组:(Order,Item,Item)

自引用查询

因此,query.join()现在可以创建别名。那给了我们什么?自引用查询!连接可以在没有任何Alias对象的情况下完成:

# standard self-referential TreeNode mapper with backref
mapper(
    TreeNode,
    tree_nodes,
    properties={
        "children": relation(
            TreeNode, backref=backref("parent", remote_side=tree_nodes.id)
        )
    },
)
# query for node with child containing "bar" two levels deep
session.query(TreeNode).join(["children", "children"], aliased=True).filter_by(
    name="bar"
)

要为沿途的每个表添加条件以进行别名连接,您可以使用from_joinpoint来继续针对相同行别名进行连接:

# search for the treenode along the path "n1/n12/n122"
# first find a Node with name="n122"
q = sess.query(Node).filter_by(name="n122")
# then join to parent with "n12"
q = q.join("parent", aliased=True).filter_by(name="n12")
# join again to the next parent with 'n1'.  use 'from_joinpoint'
# so we join from the previous point, instead of joining off the
# root table
q = q.join("parent", aliased=True, from_joinpoint=True).filter_by(name="n1")
node = q.first()
query.populate_existing()

query.load()(或session.refresh())的急切版本。如果查询中加载的每个实例,包括所有急切加载的项,已经存在于会话中,则立即刷新它们:

session.query(Blah).populate_existing().all()

关系

嵌入在更新/插入中的 SQL 子句

对于内联执行 SQL 子句,嵌入在flush()期间的 UPDATE 或 INSERT 中:

myobject.foo = mytable.c.value + 1
user.pwhash = func.md5(password)
order.hash = text("select hash from hashing_table")

操作后使用延迟加载器设置列属性,以便在下次访问时发出加载新值的 SQL。

自引用和循环急加载

由于我们的别名技术已经提高,relation()可以在同一张表上任意次进行连接;您告诉它您想要深入多少层。让我们更清楚地展示自引用的TreeNode

nodes = Table(
    "nodes",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("parent_id", Integer, ForeignKey("nodes.id")),
    Column("name", String(30)),
)
class TreeNode(object):
    pass
mapper(
    TreeNode,
    nodes,
    properties={"children": relation(TreeNode, lazy=False, join_depth=3)},
)

当我们说:

create_session().query(TreeNode).all()

? 通过别名进行连接,从父级深入三层:

SELECT
nodes_3.id  AS  nodes_3_id,  nodes_3.parent_id  AS  nodes_3_parent_id,  nodes_3.name  AS  nodes_3_name,
nodes_2.id  AS  nodes_2_id,  nodes_2.parent_id  AS  nodes_2_parent_id,  nodes_2.name  AS  nodes_2_name,
nodes_1.id  AS  nodes_1_id,  nodes_1.parent_id  AS  nodes_1_parent_id,  nodes_1.name  AS  nodes_1_name,
nodes.id  AS  nodes_id,  nodes.parent_id  AS  nodes_parent_id,  nodes.name  AS  nodes_name
FROM  nodes  LEFT  OUTER  JOIN  nodes  AS  nodes_1  ON  nodes.id  =  nodes_1.parent_id
LEFT  OUTER  JOIN  nodes  AS  nodes_2  ON  nodes_1.id  =  nodes_2.parent_id
LEFT  OUTER  JOIN  nodes  AS  nodes_3  ON  nodes_2.id  =  nodes_3.parent_id
ORDER  BY  nodes.oid,  nodes_1.oid,  nodes_2.oid,  nodes_3.oid

注意漂亮干净的别名名称。连接不在乎是否针对同一立即表或一些其他对象,然后循环回开始。当指定join_depth时,任何类型的急加载链都可以循环回自身。当不存在时,急加载在遇到循环时会自动停止。


SqlAlchemy 2.0 中文文档(八十一)(4)https://developer.aliyun.com/article/1559916

相关文章
|
5月前
|
SQL 存储 缓存
SqlAlchemy 2.0 中文文档(八十一)(1)
SqlAlchemy 2.0 中文文档(八十一)
39 2
|
5月前
|
SQL 关系型数据库 测试技术
SqlAlchemy 2.0 中文文档(四十)(5)
SqlAlchemy 2.0 中文文档(四十)
79 2
|
5月前
|
SQL 关系型数据库 测试技术
SqlAlchemy 2.0 中文文档(四十)(3)
SqlAlchemy 2.0 中文文档(四十)
34 1
|
5月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(七十三)(4)
SqlAlchemy 2.0 中文文档(七十三)
50 2
|
5月前
|
存储 SQL Java
SqlAlchemy 2.0 中文文档(八十一)(4)
SqlAlchemy 2.0 中文文档(八十一)
37 1
|
5月前
|
SQL Oracle 关系型数据库
SqlAlchemy 2.0 中文文档(八十一)(5)
SqlAlchemy 2.0 中文文档(八十一)
39 1
|
5月前
|
SQL Oracle 关系型数据库
SqlAlchemy 2.0 中文文档(八十一)(2)
SqlAlchemy 2.0 中文文档(八十一)
38 1
|
5月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(四十)(1)
SqlAlchemy 2.0 中文文档(四十)
53 1
|
5月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(四十)(2)
SqlAlchemy 2.0 中文文档(四十)
68 1
|
5月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(七十二)(5)
SqlAlchemy 2.0 中文文档(七十二)
42 1