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
的所有行为以及 assignmapper
的 query
和 __init__
方法都移动到了新的 scoped_session()
函数中,该函数与 sessionmaker
和 create_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