SqlAlchemy 2.0 中文文档(八十一)(3)https://developer.aliyun.com/article/1559911
复合类型
这是来自 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
。对dict
来说,不再需要更改__iter__
,新的内置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
更新/插入中嵌入的 SQL 子句
对于嵌入在flush()
期间的 UPDATE 或 INSERT 中的 SQL 子句的内联执行:
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()
关系
relation()
返回一个用于所有读取操作的实时 Query
对象。写操作仅限于 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
SqlAlchemy 2.0 中文文档(八十一)(5)https://developer.aliyun.com/article/1559920