SqlAlchemy 2.0 中文文档(十一)(2)https://developer.aliyun.com/article/1562988
声明式 vs. 命令式形式
随着 SQLAlchemy 的发展,不同的 ORM 配置风格已经出现。对于本节及其他使用带注解的声明式映射的示例,相应的非带注解形式应使用所需的类或字符串类名作为传递给relationship()
的第一个参数。下面的示例说明了本文档中使用的形式,这是一个完全声明式的示例,使用PEP 484注解,其中relationship()
构造还从Mapped
注解中推断目标类和集合类型,这是 SQLAlchemy 声明式映射的最现代形式:
class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[List["Child"]] = relationship(back_populates="parent") class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id")) parent: Mapped["Parent"] = relationship(back_populates="children")
相比之下,使用不带注解的声明式映射更像是更“经典”的映射形式,其中relationship()
需要直接传递所有参数,如下例所示:
class Parent(Base): __tablename__ = "parent_table" id = mapped_column(Integer, primary_key=True) children = relationship("Child", back_populates="parent") class Child(Base): __tablename__ = "child_table" id = mapped_column(Integer, primary_key=True) parent_id = mapped_column(ForeignKey("parent_table.id")) parent = relationship("Parent", back_populates="children")
最后,使用命令式映射,这是在声明式之前 SQLAlchemy 的原始映射形式(尽管仍然被少数用户偏爱),以上配置如下所示:
registry.map_imperatively( Parent, parent_table, properties={"children": relationship("Child", back_populates="parent")}, ) registry.map_imperatively( Child, child_table, properties={"parent": relationship("Parent", back_populates="children")}, )
此外,对于非带注解的映射,默认的集合样式是list
。要使用set
或其他集合而不带注解,可以使用relationship.collection_class
参数进行指示:
class Parent(Base): __tablename__ = "parent_table" id = mapped_column(Integer, primary_key=True) children = relationship("Child", collection_class=set, ...)
有关relationship()
的集合配置详细信息,请参阅自定义集合访问。
根据需要,将注意到注释和非注释/命令式样式之间的其他差异。
一对多
一对多关系在子表上放置一个外键,引用父表。然后在父表上指定relationship()
,作为对由子表表示的项目集合的引用:
class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[List["Child"]] = relationship() class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id"))
在一对多关系中建立双向关系时,其中“反向”端是多对一,需要指定一个额外的relationship()
并使用relationship.back_populates
参数将两者连接起来,其中使用每个relationship()
的属性名称作为另一个relationship.back_populates
的值:
class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[List["Child"]] = relationship(back_populates="parent") class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id")) parent: Mapped["Parent"] = relationship(back_populates="children")
Child
将获得一个具有多对一语义的parent
属性。
使用集合、列表或其他集合类型进行一对多关系
使用带注释的声明性映射时,用于relationship()
的集合类型是从传递给Mapped
容器类型的集合类型派生的。上一节的示例可以编写为使用set
而不是list
来表示Parent.children
集合,使用Mapped[Set["Child"]]
:
class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[Set["Child"]] = relationship(back_populates="parent")
当使用非注释形式,包括命令式映射时,可以使用relationship.collection_class
参数传递要用作集合的 Python 类。
另请参见
自定义集合访问 - 包含了更多关于集合配置的细节,包括一些将relationship()
映射到字典的技术。
配置一对多关系的删除行为
通常情况下,当其所属的Parent
被删除时,所有Child
对象都应该被删除。为了配置这种行为,使用 delete 中描述的delete
级联选项。另一个选项是,当Child
对象与其父对象解除关联时,可以删除Child
对象本身。这种行为在 delete-orphan 中描述。
另请参见
delete
使用 ORM 关系的外键 ON DELETE 级联
delete-orphan
使用集合、列表或其他集合类型进行一对多关系
使用带注释的声明性映射,从传递给Mapped
容器类型的集合类型派生出用于relationship()
的集合类型。可以写一个例子,以使用set
而不是list
来表示Parent.children
集合,使用Mapped[Set["Child"]]
:
class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[Set["Child"]] = relationship(back_populates="parent")
在使用非注释形式,包括命令式映射时,可以使用relationship.collection_class
参数传递要用作集合的 Python 类。
另请参阅
自定义集合访问 - 包含有关集合配置的更多详细信息,包括一些将relationship()
映射到字典的技术。
配置一对多的删除行为
通常情况下,当其所属的Parent
被删除时,所有的Child
对象都应该被删除。要配置此行为,可以使用在删除中描述的delete
级联选项。另一个选项是当Child
对象与其父对象解除关联时,它本身也可以被删除。这种行为在删除孤儿中描述。
另请参阅
删除
使用 ORM 关系的外键 ON DELETE 级联
删除孤儿
多对一
多对一在父表中放置了一个引用子表的外键。在父级上声明relationship()
,将创建一个新的标量持有属性:
class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) child_id: Mapped[int] = mapped_column(ForeignKey("child_table.id")) child: Mapped["Child"] = relationship() class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True)
上面的示例显示了一个假定为非空的多对一关系;下一节,可空多对一,介绍了一个可空版本。
通过在两个方向添加第二个relationship()
并在两个方向上应用relationship.back_populates
参数,使用每个relationship()
的属性名称作为另一个relationship.back_populates
的值,实现了双向行为:
class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) child_id: Mapped[int] = mapped_column(ForeignKey("child_table.id")) child: Mapped["Child"] = relationship(back_populates="parents") class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parents: Mapped[List["Parent"]] = relationship(back_populates="child")
可空多对一
在上述示例中,Parent.child
的关系未被类型化为允许 None
;这是因为 Parent.child_id
列本身不可为空,因为它被类型化为 Mapped[int]
。如果我们想要 Parent.child
成为一个可为空的多对一关系,我们可以将 Parent.child_id
和 Parent.child
都设置为 Optional[]
,在这种情况下,配置将如下所示:
from typing import Optional class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) child_id: Mapped[Optional[int]] = mapped_column(ForeignKey("child_table.id")) child: Mapped[Optional["Child"]] = relationship(back_populates="parents") class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parents: Mapped[List["Parent"]] = relationship(back_populates="child")
在上述代码中,用于 Parent.child_id
的列将在 DDL 中被创建以允许 NULL
值。在使用 mapped_column()
进行显式类型声明时,对 child_id: Mapped[Optional[int]]
的规定等效于将 Column.nullable
设置为 True
在 Column
上,而 child_id: Mapped[int]
等效于将其设置为 False
。有关此行为的背景信息,请参阅 mapped_column() 从 Mapped 注释中推导出数据类型和可为空性。
提示
如果使用 Python 3.10 或更高版本,PEP 604 语法更方便,可使用 | None
来指示可选类型,结合 PEP 563 推迟注释评估,以便不需要字符串引号类型,这将如下所示:
from __future__ import annotations class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) child_id: Mapped[int | None] = mapped_column(ForeignKey("child_table.id")) child: Mapped[Child | None] = relationship(back_populates="parents") class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parents: Mapped[List[Parent]] = relationship(back_populates="child") ```### 可为空的多对一关系 在上述示例中,`Parent.child` 的关系未被类型化为允许 `None`;这是因为 `Parent.child_id` 列本身不可为空,因为它被类型化为 `Mapped[int]`。如果我们想要 `Parent.child` 成为一个**可为空的**多对一关系,我们可以将 `Parent.child_id` 和 `Parent.child` 都设置为 `Optional[]`,在这种情况下,配置将如下所示: ```py from typing import Optional class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) child_id: Mapped[Optional[int]] = mapped_column(ForeignKey("child_table.id")) child: Mapped[Optional["Child"]] = relationship(back_populates="parents") class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parents: Mapped[List["Parent"]] = relationship(back_populates="child")
在上述代码中,用于 Parent.child_id
的列将在 DDL 中被创建以允许 NULL
值。在使用 mapped_column()
进行显式类型声明时,对 child_id: Mapped[Optional[int]]
的规定等效于将 Column.nullable
设置为 True
在 Column
上,而 child_id: Mapped[int]
等效于将其设置为 False
。有关此行为的背景信息,请参阅 mapped_column() 从 Mapped 注释中推导出数据类型和可为空性。
提示
如果使用 Python 3.10 或更高版本,PEP 604 语法更方便地使用| None
来指示可选类型,与PEP 563推迟的注释评估结合使用,以便不需要字符串引号的类型,会是这样的:
from __future__ import annotations class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) child_id: Mapped[int | None] = mapped_column(ForeignKey("child_table.id")) child: Mapped[Child | None] = relationship(back_populates="parents") class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parents: Mapped[List[Parent]] = relationship(back_populates="child")
一对一
One To One 本质上是一个从外键角度来看的一对多关系,但表示在任何时候只会有一行指向特定父行的行。
当使用带注释的映射和Mapped
时,“一对一”约定通过在关系的两侧应用非集合类型到Mapped
注释来实现,这将暗示 ORM 不应在任一侧使用集合,如下面的示例所示:
class Parent(Base): __tablename__ = "parent_table" id: Mapped[int] = mapped_column(primary_key=True) child: Mapped["Child"] = relationship(back_populates="parent") class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id")) parent: Mapped["Parent"] = relationship(back_populates="child")
上面的例子中,当我们加载一个Parent
对象时,Parent.child
属性将引用一个单个的Child
对象而不是一个集合。如果我们用一个新的Child
对象替换Parent.child
的值,ORM 的工作单元过程将用新的对象替换之前的对象,将之前的child.parent_id
列默认设置为 NULL,除非设置了特定的级联行为。
提示
正如前面提到的,ORM 将“一对一”模式视为一种约定,其中它假设当它加载Parent.child
属性时,它只会得到一行返回。如果返回多行,ORM 将发出警告。
然而,上述关系中的Child.parent
方面仍然是一个“多对一”的关系。独自使用时,它不会检测到多个Child
的赋值,除非设置了relationship.single_parent
参数,这可能是有用的:
class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id")) parent: Mapped["Parent"] = relationship(back_populates="child", single_parent=True)
在设置此参数之外,“一对多”方面(这里按照惯例是一对一)也不会可靠地检测到多个Child
关联到单个Parent
的情况,比如多个Child
对象是挂起的并且不是数据库持久的情况。
是否使用了relationship.single_parent
,建议数据库模式包括一个唯一约束来指示Child.parent_id
列应该是唯一的,以确保在数据库级别只有一行Child
可能指向特定的Parent
行(有关__table_args__
元组语法的背景,请参阅声明式表配置):
from sqlalchemy import UniqueConstraint class Child(Base): __tablename__ = "child_table" id: Mapped[int] = mapped_column(primary_key=True) parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id")) parent: Mapped["Parent"] = relationship(back_populates="child") __table_args__ = (UniqueConstraint("parent_id"),)
新版本 2.0 中:relationship()
构造可以从给定的Mapped
注解推导出relationship.uselist
参数的有效值。
对于非注解配置设置 uselist=False
当在没有使用Mapped
注解的情况下使用relationship()
时,可以通过将通常是“多”端的relationship.uselist
参数设置为False
来启用一对一模式,如下面的非注解式声明配置所示:
class Parent(Base): __tablename__ = "parent_table" id = mapped_column(Integer, primary_key=True) child = relationship("Child", uselist=False, back_populates="parent") class Child(Base): __tablename__ = "child_table" id = mapped_column(Integer, primary_key=True) parent_id = mapped_column(ForeignKey("parent_table.id")) parent = relationship("Parent", back_populates="child")
对于非注解配置设置 uselist=False
当在没有使用Mapped
注解的情况下使用relationship()
时,可以通过将通常是“多”端的relationship.uselist
参数设置为False
来启用一对一模式,如下面的非注解式声明配置所示:
class Parent(Base): __tablename__ = "parent_table" id = mapped_column(Integer, primary_key=True) child = relationship("Child", uselist=False, back_populates="parent") class Child(Base): __tablename__ = "child_table" id = mapped_column(Integer, primary_key=True) parent_id = mapped_column(ForeignKey("parent_table.id")) parent = relationship("Parent", back_populates="child")
SqlAlchemy 2.0 中文文档(十一)(4)https://developer.aliyun.com/article/1562990