SQLAlchemy 0.4 有什么新东西?
关于本文档
本文档描述了 SQLAlchemy 版本 0.3(2007 年 10 月 14 日最后发布)与 SQLAlchemy 版本 0.4(2008 年 10 月 12 日最后发布)之间的变化。
文档日期:2008 年 3 月 21 日
先说最重要的事情
如果您使用了任何 ORM 特性,请确保从 sqlalchemy.orm
中导入:
from sqlalchemy import * from sqlalchemy.orm import *
其次,您曾经在任何地方使用 engine=
,connectable=
,bind_to=
,something.engine
,metadata.connect()
,现在使用 bind
:
myengine = create_engine("sqlite://") meta = MetaData(myengine) meta2 = MetaData() meta2.bind = myengine session = create_session(bind=myengine) statement = select([table], bind=myengine)
明白了吗?好!你现在(95%)兼容了 0.4 版本。如果你在使用 0.3.10,你可以立即做出这些更改;它们也会在那里起作用。
模块导入
在 0.3 版本中,“from sqlalchemy import *
”会将所有 sqlalchemy 的子模块导入到您的命名空间中。版本 0.4 不再将子模块导入到命名空间中。这可能意味着您需要在您的代码中添加额外的导入。
在 0.3 版本中,这段代码可以工作:
from sqlalchemy import * class UTCDateTime(types.TypeDecorator): pass
在 0.4 版本中,必须这样做:
from sqlalchemy import * from sqlalchemy import types class UTCDateTime(types.TypeDecorator): pass
对象关系映射
查询
新的查询 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")
以上代码将创建两个别名从 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()
关系
嵌入在更新/插入中的 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
时,任何类型的贪婪加载链都可以在自身上循环。当不存在时,贪婪加载在遇到循环时会自动停止。
复合类型
这是来自 Hibernate 阵营的一个例子。复合类型允许您定义一个由多个列(或一个列,如果您愿意的话)组成的自定义数据类型。让我们定义一个新类型,Point
。存储一个 x/y 坐标:
class Point(object): def __init__(self, x, y): self.x = x self.y = y def __composite_values__(self): return self.x, self.y def __eq__(self, other): return other.x == self.x and other.y == self.y def __ne__(self, other): return not self.__eq__(other)
Point
对象的定义方式是特定于自定义类型的;构造函数接受一个参数列表,并且 __composite_values__()
方法生成这些参数的序列。顺序将与我们的映射器匹配,我们马上就会看到。
让我们创建一个存储每行两个点的顶点表:
vertices = Table( "vertices", metadata, Column("id", Integer, primary_key=True), Column("x1", Integer), Column("y1", Integer), Column("x2", Integer), Column("y2", Integer), )
然后,映射它!我们将创建一个存储两个 Point
对象的 Vertex
对象:
class Vertex(object): def __init__(self, start, end): self.start = start self.end = end mapper( Vertex, vertices, properties={ "start": composite(Point, vertices.c.x1, vertices.c.y1), "end": composite(Point, vertices.c.x2, vertices.c.y2), }, )
一旦设置了您的复合类型,它就可以像任何其他类型一样使用:
v = Vertex(Point(3, 4), Point(26, 15)) session.save(v) session.flush() # works in queries too q = session.query(Vertex).filter(Vertex.start == Point(3, 4))
如果您想定义映射属性在表达式中生成 SQL 子句的方式,请创建自己的 sqlalchemy.orm.PropComparator
子类,定义任何常见操作符(如 __eq__()
,__le__()
等),并将其发送到 composite()
。复合类型也可以作为主键,并且可以在 query.get()
中使用:
# a Document class which uses a composite Version # object as primary key document = query.get(Version(1, "a"))
dynamic_loader()
关系
一个返回所有读操作的实时 Query
对象的 relation()
。写操作仅限于 append()
和 remove()
,对集合的更改在会话刷新之前不可见。此功能在“自动刷新”会话中特别方便,该会话会在每次查询之前刷新。
mapper( Foo, foo_table, properties={ "bars": dynamic_loader( Bar, backref="foo", # <other relation() opts> ) }, ) session = create_session(autoflush=True) foo = session.query(Foo).first() foo.bars.append(Bar(name="lala")) for bar in foo.bars.filter(Bar.name == "lala"): print(bar) session.commit()
新选项:undefer_group()
,eagerload_all()
一些方便的查询选项。undefer_group()
将整个“延迟”列组标记为未延迟:
mapper( Class, table, properties={ "foo": deferred(table.c.foo, group="group1"), "bar": deferred(table.c.bar, group="group1"), "bat": deferred(table.c.bat, group="group1"), }, ) session.query(Class).options(undefer_group("group1")).filter(...).all()
eagerload_all()
设置一系列属性为一次性急切加载:
mapper(Foo, foo_table, properties={"bar": relation(Bar)}) mapper(Bar, bar_table, properties={"bat": relation(Bat)}) mapper(Bat, bat_table) # eager load bar and bat session.query(Foo).options(eagerload_all("bar.bat")).filter(...).all()
新的集合 API
集合不再由{{{InstrumentedList}}}代理代理,对成员、方法和属性的访问是直接的。装饰器现在拦截进入和离开集合的对象,并且现在可以轻松编写一个自定义集合类来管理自己的成员。灵活的装饰器还取代了 0.3 中自定义集合的命名方法接口,允许任何类轻松适应用作集合容器。
基于字典的集合现在更容易使用,完全类似于dict
。不再需要更改__iter__
以用于dict
,新的内置dict
类型涵盖了许多需求:
# use a dictionary relation keyed by a column relation(Item, collection_class=column_mapped_collection(items.c.keyword)) # or named attribute relation(Item, collection_class=attribute_mapped_collection("keyword")) # or any function you like relation(Item, collection_class=mapped_collection(lambda entity: entity.a + entity.b))
现有的 0.3 类似dict
和自由形式对象派生的集合类需要更新以适应新的 API。在大多数情况下,这只是在类定义中添加一些装饰器的问题。
从外部表/子查询映射的关系
这个功能在 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 以来最接近的感觉(即,输入最少)。
在定义engine
(或任何地方)的地方配置自己的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()
会话再次默认使用弱引用
默认情况下,在 Session 上将 weak_identity_map 标志设置为 True
。外部解除引用并超出范围的实例将自动从会话中移除。但是,具有“脏”更改的项目将保持强引用,直到这些更改被刷新,此时对象将恢复为弱引用(这适用于像可选属性这样的‘可变’类型)。将 weak_identity_map 设置为 False
将为那些像缓存一样使用会话的人恢复旧的强引用行为。
自动事务会话
正如您可能已经注意到的,我们在 Session
上调用了 commit()
。标志 transactional=True
意味着 Session
总是处于事务中,commit()
永久保存。
自动刷新会话
此外,autoflush=True
意味着 Session
在每次 query
之前都会执行 flush()
,以及在调用 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 会导致编译错误。
SqlAlchemy 2.0 中文文档(八十一)(2)https://developer.aliyun.com/article/1559903