SqlAlchemy 2.0 中文文档(九)(4)

简介: SqlAlchemy 2.0 中文文档(九)

SqlAlchemy 2.0 中文文档(九)(3)https://developer.aliyun.com/article/1560674


新版本 2.0 中新增。

在构建任何类型的继承层次结构时,映射类可以包含设置为TrueMapper.polymorphic_abstract参数,表示该类应该正常映射,但不期望直接实例化,并且不包括Mapper.polymorphic_identity。然后可以声明这个映射类的子类,这些子类本身可以包含Mapper.polymorphic_identity,因此可以正常使用。这允许一系列子类被一个被认为是层次结构内“抽象”的公共基类引用,无论是在查询中还是在relationship()声明中。这种用法与在 Declarative 中使用 abstract 属性的用法不同,后者使目标类完全未映射,因此不能作为一个映射类单独使用。Mapper.polymorphic_abstract可以应用于层次结构中的任何类或类,包括一次在多个级别上。

举个例子,假设要将ManagerPrincipal都归类到一个超类Executive下,而EngineerSysadmin则归类到一个超类Technologist下。ExecutiveTechnologist都不会被实例化,因此没有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"}

在上面的示例中,新类TechnologistExecutive都是普通的映射类,并指示要添加到超类中的新列executive_backgroundcompetencies。然而,它们都缺少Mapper.polymorphic_identity的设置;这是因为不期望直接实例化TechnologistExecutive;我们总是会有ManagerPrincipalEngineerSysAdmin中的一个。然而,我们可以查询PrincipalTechnologist角色,并使它们成为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') 

抽象映射的TechnologistExecutive抽象映射类也可以成为relationship()映射的目标,就像任何其他映射类一样。我们可以扩展上述示例以包括Company,并分别添加Company.technologistsCompany.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.technologistsCompany.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不会被复制到由我们映射的ManagerEngineer表中。
  • 虽然EngineerManager类在与Employee的继承关系中被映射,但它们仍然不包括多态加载。也就是说,如果我们查询Employee对象,managerengineer表根本不会被查询。

具体多态加载配置

具有具体继承的多态加载要求针对应该具有多态加载的每个基类配置专门的 SELECT。此 SELECT 需要能够单独访问所有映射的表,并且通常是使用 SQLAlchemy 辅助程序polymorphic_union()构造的 UNION 语句。

如编写继承映射的 SELECT 语句所述,任何类型的映射器继承配置都可以使用Mapper.with_polymorphic参数默认配置从特殊的可选项中加载。当前公共 API 要求在首次构造Mapper时设置此参数。

但是,在声明式的情况下,映射器和被映射的Table同时创建,即在定义映射类的那一刻。这意味着暂时无法提供Mapper.with_polymorphic参数,因为子类对应的Table对象尚未定义。

有一些可用的策略来解决这个循环,然而声明性提供了帮助类ConcreteBaseAbstractConcreteBase,它们在幕后处理这个问题。

使用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 的继承映射功能;我们可以正常加载和持久化ManagerEngineer的实例。然而,当我们需要多态地查询时,也就是说,我们想要发出select(Employee)并返回ManagerEngineer实例的集合时,情况就会发生变化。这让我们重新进入具体继承的领域,我们必须针对Employee构建一个特殊的映射器才能实现这一点。

要修改我们的具体继承示例以说明一个能够进行多态加载的“抽象”基类,我们将只有一个 engineer 和一个 manager 表,没有 employee 表,但是 Employee 映射器将直接映射到“多态联合”,而不是在 Mapper.with_polymorphic 参数中本地指定它。

为了帮助实现这一点,声明性提供了一个名为 AbstractConcreteBaseConcreteBase 类的变体,它可以自动实现这一点:

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 更复杂,因为整个基类的映射必须延迟到所有子类都声明完毕。有了上述的映射,只有 ManagerEngineer 的实例才能被持久化;对 Employee 类进行查询将始终产生 ManagerEngineer 对象。

使用上述的映射,可以根据 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_dataEngineer.engineer_info 仅存在于它们各自的子类中。当未设置 AbstractConcreteBase.strict_attrs 时,所有子类的属性如 Manager.manager_dataEngineer.engineer_info 都会映射到基类 Employee 中。这是一种遗留模式,对于查询可能更方便,但会导致所有子类共享整个层次结构的完整属性集;在上述示例中,如果不使用 AbstractConcreteBase.strict_attrs,将会生成无用的 Engineer.manager_nameManager.engineer_info 属性。

新版本 2.0 中:增加了 AbstractConcreteBase.strict_attrs 参数到 AbstractConcreteBase,以生成更清晰的映射;默认值为 False,以允许遗留映射在 1.x 版本中继续正常工作。

另请参阅

AbstractConcreteBase

经典和半经典具体多态配置

通过ConcreteBaseAbstractConcreteBase说明的声明性配置等同于另外两种明确使用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

相关文章
|
4月前
|
测试技术 API 数据库
SqlAlchemy 2.0 中文文档(九)(5)
SqlAlchemy 2.0 中文文档(九)
27 0
|
4月前
|
SQL 测试技术 API
SqlAlchemy 2.0 中文文档(一)(1)
SqlAlchemy 2.0 中文文档(一)
156 1
SqlAlchemy 2.0 中文文档(一)(1)
|
4月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(五)(5)
SqlAlchemy 2.0 中文文档(五)
48 4
|
4月前
|
存储 SQL API
SqlAlchemy 2.0 中文文档(四)(5)
SqlAlchemy 2.0 中文文档(四)
32 3
|
4月前
|
SQL 存储 API
SqlAlchemy 2.0 中文文档(四)(3)
SqlAlchemy 2.0 中文文档(四)
41 3
|
4月前
|
SQL API 数据库
SqlAlchemy 2.0 中文文档(一)(5)
SqlAlchemy 2.0 中文文档(一)
104 1
|
4月前
|
SQL 存储 数据库
SqlAlchemy 2.0 中文文档(一)(3)
SqlAlchemy 2.0 中文文档(一)
72 1
|
4月前
|
测试技术 API 数据库
SqlAlchemy 2.0 中文文档(十)(4)
SqlAlchemy 2.0 中文文档(十)
62 1
|
4月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(二)(2)
SqlAlchemy 2.0 中文文档(二)
66 2
|
4月前
|
存储 Python
SqlAlchemy 2.0 中文文档(七)(5)
SqlAlchemy 2.0 中文文档(七)
25 1