ORM 映射类概述¶
ORM 类映射配置概述。
对于刚接触 SQLAlchemy ORM 和/或 Python 的读者,
建议浏览
ORM 快速入门,最好完成
SQLAlchemy Unified Tutorial,其中 ORM 配置首次在
使用 ORM 声明式表单定义表元数据。
ORM 映射样式¶
SQLAlchemy 具有两种不同风格的 mapper 配置,然后具有有关如何设置它们的更多子选项。映射器样式的可变性是为了适应各种开发人员首选项,包括用户定义类的抽象程度(从如何映射到关系架构表和列)、正在使用的类层次结构类型(包括是否存在自定义元类方案),以及最后是否存在其他类插桩方法,例如是否同时使用 Python 数据类。
在现代 SQLAlchemy 中,这些样式之间的差异大多是表面的;当使用特定的 SQLAlchemy 配置样式来表示映射类的意图时,映射类的内部过程对每个类的进行方式大致相同,其中最终结果始终是用户定义的类,该类具有针对可选单元配置的 Mapper
,通常由 Table
对象表示,并且类本身已被检测以包含与类级别以及该类的实例上的关系作相关的行为。由于该过程在所有情况下基本相同,因此从不同样式映射的类始终可以相互完全互作。协议 MappedClassProtocol
可用于在使用类型检查器(如 mypy)时指示映射的类。
原始映射 API 通常称为 “经典” 样式,而自动化程度更高的映射样式称为 “声明性” 样式。SQLAlchemy 现在将这两种映射样式称为命令式映射
和声明性映射。
无论使用哪种映射样式,从 SQLAlchemy 1.4 开始的所有 ORM 映射都源自一个称为 registry
的对象,该对象是 Map 类的 registry。使用此注册表,可以将一组 mapper 配置最终确定为一组,并且特定注册表中的类可以在配置过程中按名称相互引用。
在 1.4 版本发生变更: 声明性映射和经典映射现在称为“声明性”和“命令性”映射,并且在内部是统一的,所有这些都源自表示相关映射集合的注册表
构造。
声明式映射¶
Declarative Mapping 是在现代 SQLAlchemy 中构造 Map 的典型方式。最常见的模式是首先使用 DeclarativeBase
超类构造一个基类。生成的 base 类在子类化时,将声明性 Map 过程应用于从它派生的所有子类,相对于默认情况下是新 base 本地的特定注册表
。下面的示例说明了声明性基的用法,然后将其用于声明性表映射:
from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
# declarative base class
class Base(DeclarativeBase):
pass
# an example mapping using the base
class User(Base):
__tablename__ = "user"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
fullname: Mapped[str] = mapped_column(String(30))
nickname: Mapped[Optional[str]]
在上面,DeclarativeBase
类用于生成一个新的基类(在 SQLAlchemy 的文档中,它通常被称为 Base
,但可以具有任何所需的名称),要映射的新类可以从该基类继承,如上所示,构造了一个新的映射类 User
。
在 2.0 版更改: DeclarativeBase
超类取代了 declarative_base()
函数和
registry.generate_base()
方法;超类方法与 PEP 484 工具集成,无需使用插件。有关迁移说明,请参阅 ORM Declarative Models。
基类引用维护相关映射类集合的注册表
对象。以及 MetaData
对象,该对象保留类映射到的 Table
对象的集合。
以下部分将进一步详细介绍主要的 Declarative 映射样式:
使用声明性基类 - 使用基类的声明性映射。
使用 Decorator 的声明性映射(无声明性基类) - 使用 Decorator 而不是基类的声明性映射。
在 Declarative 映射类的范围内,还有两种 Table
元数据的声明方式。这些包括:
使用 mapped_column() 的声明性表 - 使用mapped_column()
指令在映射类中内联声明表列(或以旧形式,直接使用Column
对象)。mapped_column()
指令也可以选择使用Mapped
类与类型注释结合使用,该类可以直接提供有关映射列的一些详细信息。column 指令与__tablename__
和 optional 结合使用__table_args__
类级别的指令将允许 Declarative 映射过程构造要映射的Table
对象。
使用命令式表进行声明性(也称为混合声明性)- 显式构造的Table
对象与以声明方式映射的类相关联,而不是单独指定表名和属性。这种映射样式是“声明性”和“命令式”映射的混合体,适用于将类映射到反射的 Table
对象,以及将类映射到现有 Core 构造(如联接和子查询)等技术。
声明式映射的文档继续在使用 Declare 映射类中。
命令式映射¶
命令式映射或经典映射是指使用 registry.map_imperatively()
方法配置映射类,其中目标类不包含任何声明性类属性。
提示
命令式映射表单是一种较少使用的映射形式,起源于 2006 年 SQLAlchemy 的第一个版本。它本质上是一种绕过 Declarative 系统以提供更“准系统”的映射系统的方法,并且不提供 PEP 484 支持等现代功能。因此,大多数文档示例都使用 Declarative 形式,建议新用户从 Declarative Table 开始
配置。
在 2.0 版更改: registry.map_imperatively()
方法现在用于创建经典映射。sqlalchemy.orm.mapper()
Standalone 函数被有效地删除。
在 “classical” 形式中,表元数据是使用
Table
结构,然后在建立注册表
实例后通过 registry.map_imperatively()
方法与 User
类关联。 通常,单个
注册表
shared 对于彼此相关的所有映射类:
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import registry
mapper_registry = registry()
user_table = Table(
"user",
mapper_registry.metadata,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("fullname", String(50)),
Column("nickname", String(12)),
)
class User:
pass
mapper_registry.map_imperatively(User, user_table)
有关 Map 属性的信息(例如与其他类的关系)通过 properties
字典提供。下面的示例说明了第二个表
对象,映射到一个名为 Address
的类,然后通过 relationship()
链接到 User
:
address = Table(
"address",
metadata_obj,
Column("id", Integer, primary_key=True),
Column("user_id", Integer, ForeignKey("user.id")),
Column("email_address", String(50)),
)
mapper_registry.map_imperatively(
User,
user,
properties={
"addresses": relationship(Address, backref="user", order_by=address.c.id)
},
)
mapper_registry.map_imperatively(Address, address)
请注意,使用 Imperative 方法映射的类是完全的
可与使用 Declarative 方法映射的 API 互换。两种系统
最终创建相同的配置,包括
表
,用户定义的类,通过
Mapper
对象。当我们谈论“行为
Mapper
“,这也包括在使用 Declarative 系统时 - 它仍在使用,只是在幕后。
映射类基本组件¶
对于所有映射形式,可以通过传递最终成为 Mapper
一部分的构造参数以多种方式配置类的映射
Object 的构造函数。 传送到
Mapper
源自给定的映射表单,包括传递给 registry.map_imperatively()
的 Imperative 的参数
mapping,或者在使用 Declarative 系统时,从组合
的表列、SQL 表达式和
关系与属性(如
__mapper_args__。
配置信息有四类一般类,其中
Mapper
类查找:
要映射的类¶
这是我们在应用程序中构造的一个类。通常对此类的结构没有限制。
映射 Python 类时,只能有一个Mapper
object 的类。[2]
当使用声明式映射样式进行映射时,要映射的类要么是声明式基类的子类,要么由装饰器或函数(如 registry.mapped()
)处理。
当使用命令式样式进行映射时,
类直接作为
map_imperatively.class_
参数。
table 或其他 from 子句对象¶
在绝大多数常见情况下,这是
表
。对于更高级的用例,它还可以引用任何类型的 FromClause
对象,最常见的替代对象是 Subquery
和 Join
对象。
当使用声明式映射样式进行映射时,主题表要么由声明式系统基于 __tablename__
属性和显示的 Column
对象生成,要么通过 __table__
属性建立。 这些
两种配置样式在
带有 mapped_column() 的声明式表和带有命令式表的声明式表(又名混合声明式)。
当使用命令式样式进行映射时,
subject table 作为
map_imperatively.local_table
参数。
与映射类的“每个类一个映射器”要求相反,作为映射主题的 Table
或其他 FromClause
对象可以与任意数量的映射相关联。Mapper
将修改直接应用于用户定义的类,但不修改给定的 Table
或其他
FromClause
中。
properties 字典¶
这是所有属性的字典
)将与 Map 类相关联。 默认情况下,
Mapper
以 ColumnProperty
的形式为从给定 Table
派生的此字典生成条目
对象,每个对象都引用映射表的单个 Column
。properties 字典还将包含所有其他类型的 MapperProperty
要配置的对象,最常见的是由 relationship()
结构生成的实例。
当使用声明式映射样式进行映射时,properties 字典由声明式系统通过扫描要映射的类来生成适当的属性。有关此过程的说明,请参阅使用 Declare Defining Mapped Properties 部分。
当使用命令式样式进行映射时,
properties 字典直接作为
properties
参数传递给 registry.map_imperatively(),
这会将其传递给
Mapper.properties
参数。
其他 mapper 配置参数¶
当使用声明式映射进行映射时
style,其他映射器配置参数通过
__mapper_args__
class 属性。使用示例可在 Mapper Configuration Options with Declare 中找到。
当使用命令式样式进行映射时,关键字参数将传递给 to registry.map_imperatively()
方法,该方法将它们传递给 Mapper
类。
映射类行为¶
在使用注册表
对象的所有映射样式中,以下行为是常见的:
默认构造函数¶
注册表
应用默认构造函数,即 __init__
方法,传递给所有没有明确拥有自己的
__init__
方法。此方法的行为是这样的,它提供了一个方便的关键字构造函数,该构造函数将接受所有已命名的属性作为可选的关键字参数。例如:
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "user"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
fullname: Mapped[str]
上述 User
类型的对象将具有一个构造函数,该构造函数允许
要创建的用户
对象为:
u1 = User(name="some name", fullname="some fullname")
提示
声明性数据类映射功能提供了一种使用 Python 数据类生成默认 __init__()
方法的替代方法,并允许高度可配置的构造函数形式。
警告
仅当在 Python 代码中构造对象时,而不是在加载或刷新对象时,才会调用类的 __init__()
方法
从数据库中。请参阅下一部分:在负载之间维护非映射状态
有关如何在加载对象时调用 Special logic 的入门知识。
包含显式 __init__()
方法的类将维护该方法,并且不会应用默认构造函数。
要更改使用的默认构造函数,可以向 registry.constructor
参数提供用户定义的 Python 可调用对象,该参数将用作默认构造函数。
构造函数也适用于命令式映射:
from sqlalchemy.orm import registry
mapper_registry = registry()
user_table = Table(
"user",
mapper_registry.metadata,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
)
class User:
pass
mapper_registry.map_imperatively(User, user_table)
上述类(如 命令式映射中所述)也将具有与注册表
关联的默认构造函数。
在 1.4 版本加入: 经典映射现在通过 registry.map_imperatively()
映射时支持标准配置级构造函数
方法。
在 loads之间保持 non-mapped 状态¶
当直接在 Python 代码中构造对象时,将调用 __init__()
映射类的方法:
u1 = User(name="some name", fullname="some fullname")
但是,当使用 ORM Session
加载对象时,不会调用 __init__()
方法:
u1 = session.scalars(select(User).where(User.name == "some name")).first()
这样做的原因是,当从数据库加载时,用于构造对象的作(在上面的示例中为 User
)更类似于反序列化,例如解封,而不是初始构造。对象的大部分重要状态不是第一次组装,而是从数据库行重新加载。
因此,要在不属于存储到数据库的数据的对象中保持状态,以便在加载和构建对象时存在此状态,下面详细介绍了两种常规方法。
使用 Python 描述符(如@property
)而不是 state,根据需要动态计算属性。
对于简单属性,这是最简单的方法,也是最不容易出错的。例如,如果具有Point.x
和Point.y
的对象Point
需要一个具有这些属性之和的属性:class Point(Base): __tablename__ = "point" id: Mapped[int] = mapped_column(primary_key=True) x: Mapped[int] y: Mapped[int] @property def x_plus_y(self): return self.x + self.y
使用动态描述符的一个优点是每次都会计算该值,这意味着它会在基础属性(在本例中为x
和y
)可能更改时保持正确的值。
上述模式的其他形式包括 Python 标准库 cached_property 装饰器(被缓存,不会每次都重新计算),以及 SQLAlchemy 的hybrid_property
装饰器,它允许也可以用于 SQL 查询的属性。
使用InstanceEvents.load()
和可选的补充方法InstanceEvents.refresh()
和InstanceEvents.refresh_flush()
.
这些是每当从数据库加载对象或在过期后刷新对象时都会调用的事件钩子。通常只需要InstanceEvents.load(),
因为非映射的本地对象状态不受过期作的影响。修改Point
的步骤 上面的示例如下所示:from sqlalchemy import event class Point(Base): __tablename__ = "point" id: Mapped[int] = mapped_column(primary_key=True) x: Mapped[int] y: Mapped[int] def __init__(self, x, y, **kw): super().__init__(x=x, y=y, **kw) self.x_plus_y = x + y @event.listens_for(Point, "load") def receive_load(target, context): target.x_plus_y = target.x + target.y
如果也使用 refresh 事件,则如果需要,可以将事件钩子堆叠在一个可调用对象之上,如下所示:@event.listens_for(Point, "load") @event.listens_for(Point, "refresh") @event.listens_for(Point, "refresh_flush") def receive_load(target, context, attrs=None): target.x_plus_y = target.x + target.y
在上面,attrs
属性将用于刷新
和refresh_flush
事件,并指示正在刷新的属性名称列表。
映射类、实例和 Mapper 的运行时自省¶
使用 registry
映射的类还将具有所有映射通用的一些属性:
__mapper__
属性将引用与类关联的Mapper
:mapper = User.__mapper__
from sqlalchemy import inspect mapper = inspect(User)
__table__
属性将引用Table
,或者更一般地引用类映射到的FromClause
对象:table = User.__table__
这个FromClause
也是在使用Mapper.local_table
Mapper
的属性:table = inspect(User).local_table
对于单表继承映射,其中类是没有自己的表的子类,Mapper.local_table
属性以及.__table__
属性将为None
。要检索在查询此类期间实际选择的 “selectable”,可以通过Mapper.selectable
属性获得:table = inspect(User).selectable
检查 Mapper 对象¶
如上一节所示,Mapper
对象是
可从任何映射的类中使用
运行时检查 API 系统。 使用
inspect()
函数,则可以从映射的类中获取 Mapper
:
>>> from sqlalchemy import inspect
>>> insp = inspect(User)
详细信息包括 Mapper.columns
:
>>> insp.columns
<sqlalchemy.util._collections.OrderedProperties object at 0x102f407f8>
这是一个命名空间,可以以列表格式或通过单个名称查看:
>>> list(insp.columns)
[Column('id', Integer(), table=<user>, primary_key=True, nullable=False), Column('name', String(length=50), table=<user>), Column('fullname', String(length=50), table=<user>), Column('nickname', String(length=50), table=<user>)]
>>> insp.columns.name
Column('name', String(length=50), table=<user>)
其他命名空间包括 Mapper.all_orm_descriptors
,它包括所有映射的属性以及 hybrid、关联代理:
>>> insp.all_orm_descriptors
<sqlalchemy.util._collections.ImmutableProperties object at 0x1040e2c68>
>>> insp.all_orm_descriptors.keys()
['fullname', 'nickname', 'name', 'id']
>>> list(insp.column_attrs)
[<ColumnProperty at 0x10403fde0; id>, <ColumnProperty at 0x10403fce8; name>, <ColumnProperty at 0x1040e9050; fullname>, <ColumnProperty at 0x1040e9148; nickname>]
>>> insp.column_attrs.name
<ColumnProperty at 0x10403fce8; name>
>>> insp.column_attrs.name.expression
Column('name', String(length=50), table=<user>)
另请参阅
检查 Map 实例¶
inspect()
函数还提供有关映射类的实例的信息。当应用于映射类的实例而不是类本身时,返回的对象称为 InstanceState
,它不仅提供指向类使用的 Mapper
的链接,而且还提供一个详细的接口,该接口提供有关实例中各个属性的状态的信息,包括它们的当前值以及这与它们的数据库加载值的关系。
给定从数据库加载的 User
类的实例:
>>> u1 = session.scalars(select(User)).first()
inspect()
函数将返回一个 InstanceState
对象:
>>> insp = inspect(u1)
>>> insp
<sqlalchemy.orm.state.InstanceState object at 0x7f07e5fec2e0>
通过这个对象,我们可以看到 Mapper
等元素:
>>> insp.mapper
<Mapper at 0x7f07e614ef50; User>
>>> insp.session
<sqlalchemy.orm.session.Session object at 0x7f07e614f160>
有关当前持久性状态的信息
对于对象:
>>> insp.persistent
True
>>> insp.pending
False
属性状态信息,例如尚未加载的属性或
延迟加载(假设地址
引用 relationship()
在映射到相关类的类上):
>>> insp.unloaded
{'addresses'}
有关属性的当前 Python 内状态的信息,例如自上次刷新以来未修改的属性:
>>> insp.unmodified
{'nickname', 'name', 'fullname', 'id'}
以及自上次刷新以来对属性的修改的特定历史记录:
>>> insp.attrs.nickname.value
'nickname'
>>> u1.nickname = "new nickname"
>>> insp.attrs.nickname.history
History(added=['new nickname'], unchanged=(), deleted=['nickname'])