SqlAlchemy 2.0 中文文档(九)(1)https://developer.aliyun.com/article/1560656
具体多态加载配置
具体继承的多态加载要求针对应具有多态加载的每个基类配置一个专门的 SELECT。此 SELECT 需要能够单独访问所有映射的表,并且通常是使用 SQLAlchemy 助手polymorphic_union()
构造的 UNION 语句。
如为继承映射编写 SELECT 语句中所讨论的,任何类型的映射继承配置都可以配置为默认从特殊可选中加载,使用Mapper.with_polymorphic
参数。当前的公共 API 要求在首次构造Mapper
时设置此参数。
但是,在使用 Declarative 的情况下,映射器和被映射的Table
同时创建,一旦定义了映射的类。这意味着由于尚未定义对应于子类的Table
对象,因此暂时无法提供Mapper.with_polymorphic
参数。
有几种可用的策略来解决这个循环,但是 Declarative 提供了处理此问题的助手类ConcreteBase
和AbstractConcreteBase
。
使用ConcreteBase
,我们可以几乎以与其他形式的继承映射相同的方式设置我们的具体映射:
from sqlalchemy.ext.declarative import ConcreteBase from sqlalchemy.orm import DeclarativeBase class Base(DeclarativeBase): pass class Employee(ConcreteBase, Base): __tablename__ = "employee" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) __mapper_args__ = { "polymorphic_identity": "employee", "concrete": True, } class Manager(Employee): __tablename__ = "manager" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) manager_data = mapped_column(String(40)) __mapper_args__ = { "polymorphic_identity": "manager", "concrete": True, } class Engineer(Employee): __tablename__ = "engineer" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) engineer_info = mapped_column(String(40)) __mapper_args__ = { "polymorphic_identity": "engineer", "concrete": True, }
上面,Declarative 在映射器“初始化”时为Employee
类设置了多态可选项;这是解决其他依赖映射器的延迟配置步骤。ConcreteBase
助手使用polymorphic_union()
函数在设置了所有其他类后创建所有具体映射表的 UNION,并然后使用已经存在的基类映射器配置此语句。
在选择时,多态联合会产生这样的查询:
session.scalars(select(Employee)).all() SELECT pjoin.id, pjoin.name, pjoin.type, pjoin.manager_data, pjoin.engineer_info FROM ( SELECT employee.id AS id, employee.name AS name, CAST(NULL AS VARCHAR(40)) AS manager_data, CAST(NULL AS VARCHAR(40)) AS engineer_info, 'employee' AS type FROM employee UNION ALL SELECT manager.id AS id, manager.name AS name, manager.manager_data AS manager_data, CAST(NULL AS VARCHAR(40)) AS engineer_info, 'manager' AS type FROM manager UNION ALL SELECT engineer.id AS id, engineer.name AS name, CAST(NULL AS VARCHAR(40)) AS manager_data, engineer.engineer_info AS engineer_info, 'engineer' AS type FROM engineer ) AS pjoin
上面的 UNION 查询需要为每个子表制造“NULL”列,以适应那些不是特定子类成员的列。
另请参阅
ConcreteBase
### 抽象具体类
到目前为止,所示的具体映射同时显示了子类和基类分别映射到各自的表中。在具体继承用例中,通常基类在数据库中不会被表示,只有子类。换句话说,基类是“抽象的”。
通常,当想要将两个不同的子类映射到各自的表中,并且将基类保持未映射时,这可以很容易地实现。在使用 Declarative 时,只需使用__abstract__
指示符声明基类:
from sqlalchemy.orm import DeclarativeBase class Base(DeclarativeBase): pass class Employee(Base): __abstract__ = True class Manager(Employee): __tablename__ = "manager" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) manager_data = mapped_column(String(40)) class Engineer(Employee): __tablename__ = "engineer" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) engineer_info = mapped_column(String(40))
上面,我们实际上并没有使用 SQLAlchemy 的继承映射功能;我们可以正常加载和持久化Manager
和Engineer
的实例。然而,当我们需要多态查询时,情况就会发生变化,也就是说,我们希望发出select(Employee)
并返回Manager
和Engineer
实例的集合。这将我们带回到具体继承的领域,我们必须针对Employee
构建一个特殊的映射器才能实现这一点。
要修改我们的具体继承示例,以说明一个能够进行多态加载的“抽象”基类,我们将只有一个engineer
和一个manager
表,没有employee
表,但是Employee
映射器将直接映射到“多态联合”,而不是在Mapper.with_polymorphic
参数中本地指定它。
为了帮助解决这个问题,Declarative 提供了一种名为AbstractConcreteBase
的ConcreteBase
类的变体,它可以自动实现这一点:
from sqlalchemy.ext.declarative import AbstractConcreteBase from sqlalchemy.orm import DeclarativeBase class Base(DeclarativeBase): pass class Employee(AbstractConcreteBase, Base): strict_attrs = True name = mapped_column(String(50)) class Manager(Employee): __tablename__ = "manager" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) manager_data = mapped_column(String(40)) __mapper_args__ = { "polymorphic_identity": "manager", "concrete": True, } class Engineer(Employee): __tablename__ = "engineer" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) engineer_info = mapped_column(String(40)) __mapper_args__ = { "polymorphic_identity": "engineer", "concrete": True, } Base.registry.configure()
上面,调用了registry.configure()
方法,这将触发实际映射Employee
类;在配置步骤之前,类没有映射,因为它将从中查询的子表尚未定义。此过程比ConcreteBase
更复杂,因为必须延迟基类的整个映射,直到所有子类都已声明。使用像上面的映射,只能持久化Manager
和Engineer
的实例;对Employee
类进行查询将始终产生Manager
和Engineer
对象。
使用上述映射,可以根据Employee
类和在其上本地声明的任何属性生成查询,例如Employee.name
:
>>> stmt = select(Employee).where(Employee.name == "n1") >>> print(stmt) SELECT pjoin.id, pjoin.name, pjoin.type, pjoin.manager_data, pjoin.engineer_info FROM ( SELECT engineer.id AS id, engineer.name AS name, engineer.engineer_info AS engineer_info, CAST(NULL AS VARCHAR(40)) AS manager_data, 'engineer' AS type FROM engineer UNION ALL SELECT manager.id AS id, manager.name AS name, CAST(NULL AS VARCHAR(40)) AS engineer_info, manager.manager_data AS manager_data, 'manager' AS type FROM manager ) AS pjoin WHERE pjoin.name = :name_1
AbstractConcreteBase.strict_attrs
参数指示 Employee
类应直接映射仅属于 Employee
类的属性,如本例中的 Employee.name
属性。其他属性,如 Manager.manager_data
和 Engineer.engineer_info
,仅存在于其相应的子类中。当未设置 AbstractConcreteBase.strict_attrs
时,所有子类属性(如 Manager.manager_data
和 Engineer.engineer_info
)都将映射到基类 Employee
。这是一种传统的使用模式,可能更方便查询,但其效果是所有子类共享整个层次结构的完整属性集;在上述示例中,不使用 AbstractConcreteBase.strict_attrs
将导致生成非实用的 Engineer.manager_name
和 Manager.engineer_info
属性。
新版 2.0 中:新增了 AbstractConcreteBase.strict_attrs
参数到 AbstractConcreteBase
中,以产生更清晰的映射;默认值为 False,以允许继续使用旧版 1.x 版本中的传统映射。
另请参阅
AbstractConcreteBase
经典和半经典具体多态配置
使用 ConcreteBase
和 AbstractConcreteBase
说明的声明性配置相当于另外两种使用 polymorphic_union()
显式的配置形式。 这些配置形式明确使用 Table
对象,以便首先创建“多态联合”,然后将其应用于映射。 这些示例旨在澄清 polymorphic_union()
函数在映射中的作用。
例如,半经典映射利用声明性,但分别建立 Table
对象:
metadata_obj = Base.metadata employees_table = Table( "employee", metadata_obj, Column("id", Integer, primary_key=True), Column("name", String(50)), ) managers_table = Table( "manager", metadata_obj, Column("id", Integer, primary_key=True), Column("name", String(50)), Column("manager_data", String(50)), ) engineers_table = Table( "engineer", metadata_obj, Column("id", Integer, primary_key=True), Column("name", String(50)), Column("engineer_info", String(50)), )
接下来,使用 polymorphic_union()
生成 UNION:
from sqlalchemy.orm import polymorphic_union pjoin = polymorphic_union( { "employee": employees_table, "manager": managers_table, "engineer": engineers_table, }, "type", "pjoin", )
使用上述 Table
对象,可以使用“半经典”样式生成映射,在此样式中,我们与 __table__
参数一起使用声明性;我们上面的多态联合通过 __mapper_args__
传递给 Mapper.with_polymorphic
参数:
class Employee(Base): __table__ = employee_table __mapper_args__ = { "polymorphic_on": pjoin.c.type, "with_polymorphic": ("*", pjoin), "polymorphic_identity": "employee", } class Engineer(Employee): __table__ = engineer_table __mapper_args__ = { "polymorphic_identity": "engineer", "concrete": True, } class Manager(Employee): __table__ = manager_table __mapper_args__ = { "polymorphic_identity": "manager", "concrete": True, }
或者,可以完全以“经典”风格使用相同的 Table
对象,而不使用声明性。 构造函数与声明性提供的类似,如下所示:
class Employee: def __init__(self, **kw): for k in kw: setattr(self, k, kw[k]) class Manager(Employee): pass class Engineer(Employee): pass employee_mapper = mapper_registry.map_imperatively( Employee, pjoin, with_polymorphic=("*", pjoin), polymorphic_on=pjoin.c.type, ) manager_mapper = mapper_registry.map_imperatively( Manager, managers_table, inherits=employee_mapper, concrete=True, polymorphic_identity="manager", ) engineer_mapper = mapper_registry.map_imperatively( Engineer, engineers_table, inherits=employee_mapper, concrete=True, polymorphic_identity="engineer", )
“抽象” 示例也可以使用“半经典”或“经典”风格进行映射。 不同之处在于,我们不再将“多态联合”应用于 Mapper.with_polymorphic
参数,而是直接将其作为我们最基本的映射器上的映射选择。 半经典映射如下所示:
from sqlalchemy.orm import polymorphic_union pjoin = polymorphic_union( { "manager": managers_table, "engineer": engineers_table, }, "type", "pjoin", ) class Employee(Base): __table__ = pjoin __mapper_args__ = { "polymorphic_on": pjoin.c.type, "with_polymorphic": "*", "polymorphic_identity": "employee", } class Engineer(Employee): __table__ = engineer_table __mapper_args__ = { "polymorphic_identity": "engineer", "concrete": True, } class Manager(Employee): __table__ = manager_table __mapper_args__ = { "polymorphic_identity": "manager", "concrete": True, }
在上面的示例中,我们与以前一样使用 polymorphic_union()
,只是省略了employee
表。
另请参阅
命令式映射 - 有关命令式或“经典”映射的背景信息
具体继承关系
在具体继承场景中,映射关系是具有挑战性的,因为不同的类不共享一个表。如果关系只涉及特定类,例如我们之前示例中的Company
和Manager
之间的关系,那么不需要特殊步骤,因为这只是两个相关表。
然而,如果Company
要与Employee
建立一对多关系,表示集合可能包括Engineer
和Manager
对象,这意味着Employee
必须具有多态加载能力,并且要关联的每个表都必须有一个外键返回到company
表。这种配置的示例如下:
from sqlalchemy.ext.declarative import ConcreteBase class Company(Base): __tablename__ = "company" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) employees = relationship("Employee") class Employee(ConcreteBase, Base): __tablename__ = "employee" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) company_id = mapped_column(ForeignKey("company.id")) __mapper_args__ = { "polymorphic_identity": "employee", "concrete": True, } class Manager(Employee): __tablename__ = "manager" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) manager_data = mapped_column(String(40)) company_id = mapped_column(ForeignKey("company.id")) __mapper_args__ = { "polymorphic_identity": "manager", "concrete": True, } class Engineer(Employee): __tablename__ = "engineer" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) engineer_info = mapped_column(String(40)) company_id = mapped_column(ForeignKey("company.id")) __mapper_args__ = { "polymorphic_identity": "engineer", "concrete": True, }
具体继承和关系的下一个复杂性涉及当我们希望Employee
、Manager
和Engineer
中的一个或全部自身引用Company
时。对于这种情况,SQLAlchemy 在 Employee
上放置一个与 Company
相关的 relationship()
时,在实例级别执行时不适用于 Manager
和 Engineer
类,而必须对每个类应用一个不同的 relationship()
。为了实现三个独立关系的双向行为,这些关系作为 Company.employees
的相反关系,使用了 relationship.back_populates
参数:
from sqlalchemy.ext.declarative import ConcreteBase class Company(Base): __tablename__ = "company" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) employees = relationship("Employee", back_populates="company") class Employee(ConcreteBase, Base): __tablename__ = "employee" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) company_id = mapped_column(ForeignKey("company.id")) company = relationship("Company", back_populates="employees") __mapper_args__ = { "polymorphic_identity": "employee", "concrete": True, } class Manager(Employee): __tablename__ = "manager" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) manager_data = mapped_column(String(40)) company_id = mapped_column(ForeignKey("company.id")) company = relationship("Company", back_populates="employees") __mapper_args__ = { "polymorphic_identity": "manager", "concrete": True, } class Engineer(Employee): __tablename__ = "engineer" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) engineer_info = mapped_column(String(40)) company_id = mapped_column(ForeignKey("company.id")) company = relationship("Company", back_populates="employees") __mapper_args__ = { "polymorphic_identity": "engineer", "concrete": True, }
上述限制与当前实现相关,包括具体继承类不共享超类的任何属性,因此需要设置不同的关系。
加载具体继承映射
具体继承加载选项有限;通常,如果在映射器上配置了多态加载,使用其中一个声明性具体混合类,就不能在当前 SQLAlchemy 版本中在查询时修改它。通常,with_polymorphic()
函数应该能够覆盖具体使用的加载样式,但由于当前限制,这还不受支持。 ## 连接表继承
在连接表继承中,类层次结构中的每个类都由一个不同的表表示。在层次结构中查询特定子类将作为 SQL JOIN 渲染其继承路径上的所有表。如果查询的类是基类,则将查询基表,同时可以选择包括其他表或允许特定于子表的属性稍后加载。
在所有情况下,给定行的最终实例化类由基类上定义的鉴别器列或 SQL 表达式确定,该列将产生与特定子类关联的标量值。
连接继承层次结构中的基类将配置具有指示多态鉴别器列的额外参数,以及可选的基类自身的多态标识符:
from sqlalchemy import ForeignKey from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column class Base(DeclarativeBase): pass class Employee(Base): __tablename__ = "employee" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] type: Mapped[str] __mapper_args__ = { "polymorphic_identity": "employee", "polymorphic_on": "type", } def __repr__(self): return f"{self.__class__.__name__}({self.name!r})"
在上面的例子中,鉴别器是type
列,可以使用Mapper.polymorphic_on
参数进行配置。该参数接受一个基于列的表达式,可以指定为要使用的映射属性的字符串名称,也可以指定为列表达式对象,如Column
或mapped_column()
构造。
鉴别器列将存储一个值,该值指示行中表示的对象类型。该列可以是任何数据类型,但字符串和整数最常见。为数据库中的特定行应用于此列的实际数据值是使用Mapper.polymorphic_identity
参数指定的,如下所述。
虽然多态鉴别器表达式不是严格必需的,但如果需要多态加载,则需要。在基表上建立一个列是实现此目的的最简单方法,但是非常复杂的继承映射可能会使用 SQL 表达式,例如 CASE 表达式,作为多态鉴别器。
注意
目前,整个继承层次结构仅可以配置一个鉴别器列或 SQL 表达式,通常在层次结构中最基本的类上。目前不支持“级联”多态鉴别器表达式。
我们接下来定义Engineer
和Manager
作为Employee
的子类。每个子类包含代表其所代表子类的唯一属性的列。每个表还必须包含主键列(或列)以及对父表的外键引用:
class Engineer(Employee): __tablename__ = "engineer" id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True) engineer_name: Mapped[str] __mapper_args__ = { "polymorphic_identity": "engineer", } class Manager(Employee): __tablename__ = "manager" id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True) manager_name: Mapped[str] __mapper_args__ = { "polymorphic_identity": "manager", }
在上面的示例中,每个映射在其映射器参数中指定了Mapper.polymorphic_identity
参数。此值填充了由基本映射器上建立的Mapper.polymorphic_on
参数指定的列。Mapper.polymorphic_identity
参数应该对整个层次结构中的每个映射类是唯一的,并且每个映射类只应有一个“标识”;如上所述,不支持一些子类引入第二个标识的“级联”标识。
ORM 使用Mapper.polymorphic_identity
设置的值来确定加载行时行属于哪个类。在上面的示例中,每个代表Employee
的行在其type
列中将有值'employee'
;同样,每个Engineer
将获得值'engineer'
,每个Manager
将获得值'manager'
。无论继承映射使用不同的联接表作为子类(如联合表继承)还是所有一个表作为单表继承,这个值都应该被持久化并在查询时对 ORM 可用。Mapper.polymorphic_identity
参数也适用于具体表继承,但实际上并没有被持久化;有关详细信息,请参阅后面的具体表继承部分。
在多态设置中,最常见的是外键约束建立在与主键本身相同的列或列上,但这并非必需;一个与主键不同的列也可以通过外键指向父类。从基本表到子类构建 JOIN 的方式也是可以直接自定义的,但这很少是必要的。
完成联合继承映射后,针对Employee
的查询将返回Employee
、Engineer
和Manager
对象的组合。新保存的Engineer
、Manager
和Employee
对象将自动填充employee.type
列,此时正确的“鉴别器”值为"engineer"
、"manager"
或"employee"
。
具有联合继承关系
与联合表继承完全支持关系。涉及联合继承类的关系应该针对与外键约束对应的层次结构中的类;在下面的示例中,由于employee
表有一个指向company
表的外键约束,关系被建立在Company
和Employee
之间:
from __future__ import annotations from sqlalchemy.orm import relationship class Company(Base): __tablename__ = "company" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] employees: Mapped[List[Employee]] = relationship(back_populates="company") class Employee(Base): __tablename__ = "employee" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] type: Mapped[str] company_id: Mapped[int] = mapped_column(ForeignKey("company.id")) company: Mapped[Company] = relationship(back_populates="employees") __mapper_args__ = { "polymorphic_identity": "employee", "polymorphic_on": "type", } class Manager(Employee): ... class Engineer(Employee): ...
如果外键约束在对应于子类的表上,则关系应该指向该子类。在下面的示例中,从manager
到company
有一个外键约束,因此建立了Manager
和Company
类之间的关系:
class Company(Base): __tablename__ = "company" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] managers: Mapped[List[Manager]] = relationship(back_populates="company") class Employee(Base): __tablename__ = "employee" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] type: Mapped[str] __mapper_args__ = { "polymorphic_identity": "employee", "polymorphic_on": "type", } class Manager(Employee): __tablename__ = "manager" id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True) manager_name: Mapped[str] company_id: Mapped[int] = mapped_column(ForeignKey("company.id")) company: Mapped[Company] = relationship(back_populates="managers") __mapper_args__ = { "polymorphic_identity": "manager", } class Engineer(Employee): ...
在上面,Manager
类将具有Manager.company
属性;Company
将具有Company.managers
属性,总是针对employee
和manager
表一起加载的连接进行加载。
加载连接继承映射
请参阅编写用于继承映射的 SELECT 语句部分,了解继承加载技术的背景,包括在映射器配置时间和查询时间配置要查询的表。
具有连接继承的关系
与连接表继承完全支持关系。涉及连接继承类的关系应该指向与外键约束对应的层次结构中的类;在下面的示例中,由于employee
表有一个指向company
表的外键约束,因此在Company
和Employee
之间建立了关系:
from __future__ import annotations from sqlalchemy.orm import relationship class Company(Base): __tablename__ = "company" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] employees: Mapped[List[Employee]] = relationship(back_populates="company") class Employee(Base): __tablename__ = "employee" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] type: Mapped[str] company_id: Mapped[int] = mapped_column(ForeignKey("company.id")) company: Mapped[Company] = relationship(back_populates="employees") __mapper_args__ = { "polymorphic_identity": "employee", "polymorphic_on": "type", } class Manager(Employee): ... class Engineer(Employee): ...
如果外键约束在对应于子类的表上,则关系应该指向该子类。在下面的示例中,从manager
到company
有一个外键约束,因此建立了Manager
和Company
类之间的关系:
class Company(Base): __tablename__ = "company" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] managers: Mapped[List[Manager]] = relationship(back_populates="company") class Employee(Base): __tablename__ = "employee" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] type: Mapped[str] __mapper_args__ = { "polymorphic_identity": "employee", "polymorphic_on": "type", } class Manager(Employee): __tablename__ = "manager" id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True) manager_name: Mapped[str] company_id: Mapped[int] = mapped_column(ForeignKey("company.id")) company: Mapped[Company] = relationship(back_populates="managers") __mapper_args__ = { "polymorphic_identity": "manager", } class Engineer(Employee): ...
在上面,Manager
类将具有Manager.company
属性;Company
将具有Company.managers
属性,总是针对employee
和manager
表一起加载的连接进行加载。
加载连接继承映射
请参阅编写用于继承映射的 SELECT 语句部分,了解继承加载技术的背景,包括在映射器配置时间和查询时间配置要查询的表。
SqlAlchemy 2.0 中文文档(九)(3)https://developer.aliyun.com/article/1560674