SqlAlchemy 2.0 中文文档(九)(3)https://developer.aliyun.com/article/1560674
使用 polymorphic_abstract
构建更深层次的层次结构
新版本 2.0 中新增。
在构建任何类型的继承层次结构时,映射类可以包含设置为True
的Mapper.polymorphic_abstract
参数,表示该类应该正常映射,但不期望直接实例化,并且不包括Mapper.polymorphic_identity
。然后可以声明这个映射类的子类,这些子类本身可以包含Mapper.polymorphic_identity
,因此可以正常使用。这允许一系列子类被一个被认为是层次结构内“抽象”的公共基类引用,无论是在查询中还是在relationship()
声明中。这种用法与在 Declarative 中使用 abstract 属性的用法不同,后者使目标类完全未映射,因此不能作为一个映射类单独使用。Mapper.polymorphic_abstract
可以应用于层次结构中的任何类或类,包括一次在多个级别上。
举个例子,假设要将Manager
和Principal
都归类到一个超类Executive
下,而Engineer
和Sysadmin
则归类到一个超类Technologist
下。Executive
和Technologist
都不会被实例化,因此没有Mapper.polymorphic_identity
。可以使用Mapper.polymorphic_abstract
来配置这些类,如下所示:
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 Executive(Employee): """An executive of the company""" executive_background: Mapped[str] = mapped_column(nullable=True) __mapper_args__ = {"polymorphic_abstract": True} class Technologist(Employee): """An employee who works with technology""" competencies: Mapped[str] = mapped_column(nullable=True) __mapper_args__ = {"polymorphic_abstract": True} class Manager(Executive): """a manager""" __mapper_args__ = {"polymorphic_identity": "manager"} class Principal(Executive): """a principal of the company""" __mapper_args__ = {"polymorphic_identity": "principal"} class Engineer(Technologist): """an engineer""" __mapper_args__ = {"polymorphic_identity": "engineer"} class SysAdmin(Technologist): """a systems administrator""" __mapper_args__ = {"polymorphic_identity": "sysadmin"}
在上面的示例中,新类Technologist
和Executive
都是普通的映射类,并指示要添加到超类中的新列executive_background
和competencies
。然而,它们都缺少Mapper.polymorphic_identity
的设置;这是因为不期望直接实例化Technologist
或Executive
;我们总是会有Manager
、Principal
、Engineer
或SysAdmin
中的一个。然而,我们可以查询Principal
和Technologist
角色,并使它们成为relationship()
的目标。下面的示例演示了用于Technologist
对象的 SELECT 语句:
session.scalars(select(Technologist)).all() SELECT employee.id, employee.name, employee.type, employee.competencies FROM employee WHERE employee.type IN (?, ?) [...] ('engineer', 'sysadmin')
抽象映射的Technologist
和Executive
抽象映射类也可以成为relationship()
映射的目标,就像任何其他映射类一样。我们可以扩展上述示例以包括Company
,并分别添加Company.technologists
和Company.principals
两个集合:
class Company(Base): __tablename__ = "company" id = Column(Integer, primary_key=True) executives: Mapped[List[Executive]] = relationship() technologists: Mapped[List[Technologist]] = relationship() class Employee(Base): __tablename__ = "employee" id: Mapped[int] = mapped_column(primary_key=True) # foreign key to "company.id" is added company_id: Mapped[int] = mapped_column(ForeignKey("company.id")) # rest of mapping is the same name: Mapped[str] type: Mapped[str] __mapper_args__ = { "polymorphic_on": "type", } # Executive, Technologist, Manager, Principal, Engineer, SysAdmin # classes from previous example would follow here unchanged
使用上述映射,我们可以分别在Company.technologists
和Company.executives
之间使用连接和关系加载技术:
session.scalars( select(Company) .join(Company.technologists) .where(Technologist.competency.ilike("%java%")) .options(selectinload(Company.executives)) ).all() SELECT company.id FROM company JOIN employee ON company.id = employee.company_id AND employee.type IN (?, ?) WHERE lower(employee.competencies) LIKE lower(?) [...] ('engineer', 'sysadmin', '%java%') SELECT employee.company_id AS employee_company_id, employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type, employee.executive_background AS employee_executive_background FROM employee WHERE employee.company_id IN (?) AND employee.type IN (?, ?) [...] (1, 'manager', 'principal')
另请参见
abstract - 声明性参数,允许在层次结构中完全取消映射 Declarative 类,同时仍从映射的超类扩展。
加载单继承映射
单表继承的加载技术大部分与用于连接表继承的技术相同,并且在这两种映射类型之间提供了很高程度的抽象,因此很容易在它们之间进行切换以及在单个层次结构中混合使用它们(只需从要单继承的子类中省略__tablename__
)。请参阅编写继承映射的 SELECT 语句和单继承映射的 SELECT 语句章节,了解有关继承加载技术的文档,包括在映射器配置时间和查询时间配置要查询的类。
具体表继承
具体表继承将每个子类映射到其自己的独立表格,每个表格包含产生该类实例所需的所有列。具体继承配置默认情况下进行非多态查询;对于特定类的查询只会查询该类的表格,并且只返回该类的实例。具体类的多态加载通过在映射器内配置一个特殊的 SELECT 来启用,该 SELECT 通常被生成为所有表的 UNION。
警告
具体表继承比连接或单表继承更加复杂,在功能上更加受限,特别是在使用关系、急加载和多态加载方面。当以多态方式使用时,会产生非常庞大的查询,其中包含的 UNION 操作不会像简单的连接那样执行良好。强烈建议如果需要灵活性的关系加载和多态加载,尽可能使用连接或单表继承。如果不需要多态加载,则可以使用普通的非继承映射,如果每个类都完全引用其自己的表格。
虽然联接和单表继承在“多态”加载方面很流畅,但在具体继承中却是一种更笨拙的事情。因此,当不需要多态加载时,具体继承更为适用。建立涉及具体继承类的关系也更加麻烦。
要将类建立为使用具体继承,请在__mapper_args__
中添加Mapper.concrete
参数。这既表示对声明式以及映射,超类表不应被视为映射的一部分:
class Employee(Base): __tablename__ = "employee" id = mapped_column(Integer, primary_key=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(50)) __mapper_args__ = { "concrete": True, } class Engineer(Employee): __tablename__ = "engineer" id = mapped_column(Integer, primary_key=True) name = mapped_column(String(50)) engineer_info = mapped_column(String(50)) __mapper_args__ = { "concrete": True, }
有两个关键点需要注意:
- 我们必须在每个子类上显式定义所有列,甚至是同名的列。例如,此处的
Employee.name
列不会被复制到由我们映射的Manager
或Engineer
表中。 - 虽然
Engineer
和Manager
类在与Employee
的继承关系中被映射,但它们仍然不包括多态加载。也就是说,如果我们查询Employee
对象,manager
和engineer
表根本不会被查询。
具体多态加载配置
具有具体继承的多态加载要求针对应该具有多态加载的每个基类配置专门的 SELECT。此 SELECT 需要能够单独访问所有映射的表,并且通常是使用 SQLAlchemy 辅助程序polymorphic_union()
构造的 UNION 语句。
如编写继承映射的 SELECT 语句所述,任何类型的映射器继承配置都可以使用Mapper.with_polymorphic
参数默认配置从特殊的可选项中加载。当前公共 API 要求在首次构造Mapper
时设置此参数。
但是,在声明式的情况下,映射器和被映射的Table
同时创建,即在定义映射类的那一刻。这意味着暂时无法提供Mapper.with_polymorphic
参数,因为子类对应的Table
对象尚未定义。
有一些可用的策略来解决这个循环,然而声明性提供了帮助类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, }
在上述示例中,声明性在映射器“初始化”时为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
### 抽象具体类
到目前为止,所示的具体映射同时显示了子类和基类映射到各自的表中。在具体继承用例中,常见的是基类在数据库中没有表示,只有子类。换句话说,基类是“抽象的”。
通常,当一个人想要将两个不同的子类映射到各自的表中,并且保留基类未映射时,这可以非常容易地实现。在使用声明性时,只需使用__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
参数中本地指定它。
为了帮助实现这一点,声明性提供了一个名为 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
表。
另请参阅
命令式映射 - 命令式或“经典”映射的背景信息
SqlAlchemy 2.0 中文文档(九)(5)https://developer.aliyun.com/article/1560681