SqlAlchemy 2.0 中文文档(一)(2)https://developer.aliyun.com/article/1563202
使用 ORM 会话执行
正如之前提到的,上面的大多数模式和示例也适用于与 ORM 一起使用,因此在这里我们将介绍这种用法,以便随着教程的进行,我们将能够以 Core 和 ORM 一起的方式来说明每个模式。
当使用 ORM 时,基本的事务/数据库交互对象称为Session
。在现代 SQLAlchemy 中,这个对象的使用方式与Connection
非常相似,实际上,当使用Session
时,它在内部引用一个Connection
,用于发出 SQL。
当Session
与非 ORM 构造一起使用时,它会通过我们提供的 SQL 语句,并且通常不会与Connection
直接执行有太大不同,因此我们可以根据我们已经学过的简单文本 SQL 操作来说明它。
Session
有几种不同的创建模式,但在这里我们将说明最基本的一种,它与使用Connection
的方式完全一致,即在上下文管理器中构造它:
>>> from sqlalchemy.orm import Session >>> stmt = text("SELECT x, y FROM some_table WHERE y > :y ORDER BY x, y") >>> with Session(engine) as session: ... result = session.execute(stmt, {"y": 6}) ... for row in result: ... print(f"x: {row.x} y: {row.y}") BEGIN (implicit) SELECT x, y FROM some_table WHERE y > ? ORDER BY x, y [...] (6,) x: 6 y: 8 x: 9 y: 10 x: 11 y: 12 x: 13 y: 14 ROLLBACK
上面的示例可以与前一节中发送参数中的示例进行比较 - 我们直接将with engine.connect() as conn
的调用替换为with Session(engine) as session
,然后像使用Connection.execute()
方法一样使用Session.execute()
方法。
同样,像Connection
一样,Session
也具有“边提交边执行”的行为,使用Session.commit()
方法,下面通过一个文本 UPDATE 语句来修改一些数据进行说明:
>>> with Session(engine) as session: ... result = session.execute( ... text("UPDATE some_table SET y=:y WHERE x=:x"), ... [{"x": 9, "y": 11}, {"x": 13, "y": 15}], ... ) ... session.commit() BEGIN (implicit) UPDATE some_table SET y=? WHERE x=? [...] [(11, 9), (15, 13)] COMMIT
在上面,我们使用绑定参数“executemany”风格的执行方式调用了一个 UPDATE 语句,该语句介绍在发送多个参数中,以“边提交边执行”方式结束了该块。
提示
Session
实际上在结束事务后并不保留 Connection
对象。下次需要对数据库执行 SQL 时,它会从 Engine
获取一个新的 Connection
。
Session
很显然比那个拥有更多的技巧,然而理解它有一个 Session.execute()
方法,该方法的使用方式与 Connection.execute()
相同,将使我们能够开始后面的示例。
另请参阅
使用会话的基础知识 - 展示了与 Session
对象的基本创建和使用模式。
处理数据库元数据
随着引擎和 SQL 执行完成,我们准备开始一些 Alchemy。SQLAlchemy Core 和 ORM 的核心元素是 SQL 表达语言,它允许流畅、可组合地构建 SQL 查询。这些查询的基础是代表数据库概念(如表和列)的 Python 对象。这些对象被统称为数据库元数据。
SQLAlchemy 中数据库元数据的最常见基础对象称为MetaData
、Table
和Column
。下面的部分将说明这些对象在 Core 导向风格和 ORM 导向风格中的使用方式。
ORM 读者,请继续关注!
与其他部分一样,Core 用户可以跳过 ORM 部分,但 ORM 用户最好从两个角度熟悉这些对象。这里讨论的Table
对象在使用 ORM 时以一种更间接的方式(也是完全 Python 类型化的方式)声明,然而,在 ORM 的配置中仍然有一个Table
对象。
使用表对象设置元数据
当我们使用关系型数据库时,数据库中的基本数据保存结构,我们从中查询的结构称为表。在 SQLAlchemy 中,数据库“表”最终由一个名为Table
的 Python 对象表示。
要开始使用 SQLAlchemy 表达语言,我们需要构建Table
对象,这些对象表示我们有兴趣使用的所有数据库表。 Table
是通过编程方式构建的,可以直接使用Table
构造函数,也可以间接地使用 ORM 映射类(稍后在使用 ORM 声明形式定义表元数据中描述)。还有一种选项可以从现有数据库加载一些或全部表信息,称为反射。
无论使用哪种方法,我们始终从一个集合开始,这个集合将是我们放置表的地方,称为 MetaData
对象。这个对象本质上是一个围绕 Python 字典的 外观,该字典存储了一系列以其字符串名称为键的 Table
对象。虽然 ORM 在获取这个集合的位置上提供了一些选项,但我们始终可以选择直接创建一个,看起来像这样:
>>> from sqlalchemy import MetaData >>> metadata_obj = MetaData()
一旦我们有了 MetaData
对象,我们可以声明一些 Table
对象。本教程将从经典的 SQLAlchemy 教程模型开始,其中有一个名为 user_account
的表,存储着网站的用户,以及一个相关的 address
表,存储着与 user_account
表中的行相关联的电子邮件地址。当完全不使用 ORM Declarative 模型时,我们直接构造每个 Table
对象,通常将每个对象分配给一个变量,这将是我们在应用程序代码中引用表的方式:
>>> from sqlalchemy import Table, Column, Integer, String >>> user_table = Table( ... "user_account", ... metadata_obj, ... Column("id", Integer, primary_key=True), ... Column("name", String(30)), ... Column("fullname", String), ... )
有了上面的例子,当我们希望编写引用数据库中 user_account
表的代码时,我们将使用 user_table
Python 变量来引用它。
Table
的组件
我们可以观察到,Python 中的 Table
构造与 SQL CREATE TABLE 语句相似;从表名开始,然后列出每个列,其中每个列都有一个名称和一个数据类型。我们上面使用的对象是:
Table
- 表示数据库表并将自己分配给MetaData
集合。Column
- 表示数据库表中的列,并将自己分配给Table
对象。Column
通常包括一个字符串名称和一个类型对象。以父Table
的Column
对象的集合通常通过位于Table.c
的关联数组来访问:
>>> user_table.c.name Column('name', String(length=30), table=<user_account>) >>> user_table.c.keys() ['id', 'name', 'fullname']
Integer
,String
- 这些类表示 SQL 数据类型,并且可以被传递给具有或没有必要被实例化的Column
。在上面的例子中,我们想要给“name”列一个长度为“30”,因此我们实例化了String(30)
。但对于“id”和“fullname”,我们没有指定这些,所以我们可以发送类本身。
另请参阅
MetaData
,Table
和Column
的参考和 API 文档位于用 MetaData 描述数据库。数据类型的参考文档位于 SQL 数据类型对象。
在接下来的一节中,我们将说明Table
的一个基本功能,即在特定数据库连接上生成 DDL。但首先,我们将声明第二个Table
。
声明简单约束
示例中的第一个Column
在user_table
中包含Column.primary_key
参数,这是一种简写技术,表示这个Column
应该是这个表的主键的一部分。主键本身通常是隐式声明的,并且由PrimaryKeyConstraint
构造表示,我们可以在Table
对象的Table.primary_key
属性上看到它:
>>> user_table.primary_key PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))
最常显式声明的约束是对应于数据库外键约束的ForeignKeyConstraint
对象。当我们声明相互关联的表时,SQLAlchemy 不仅使用这些外键约束声明在向数据库发送 CREATE 语句时将其发送出去,而且还用于帮助构造 SQL 表达式。
一个涉及目标表上仅一个列的ForeignKeyConstraint
通常使用列级别的速记符号通过ForeignKey
对象声明。下面我们声明了一个将具有引用user
表的外键约束的第二个表address
:
>>> from sqlalchemy import ForeignKey >>> address_table = Table( ... "address", ... metadata_obj, ... Column("id", Integer, primary_key=True), ... Column("user_id", ForeignKey("user_account.id"), nullable=False), ... Column("email_address", String, nullable=False), ... )
上面的表还包含了第三种约束类型,在 SQL 中是“NOT NULL”约束,在上面使用Column.nullable
参数进行指示。
提示
在Column
定义中使用ForeignKey
对象时,我们可以省略该Column
的数据类型;它会自动从相关列的数据类型中推断出来,在上面的示例中是user_account.id
列的Integer
数据类型。
在下一节中,我们将发出user
和address
表的完整 DDL 以查看完成的结果。
发出 DDL 到数据库
我们已经构建了一个对象结构,代表数据库中的两个数据库表,在根MetaData
对象开始,然后进入两个Table
对象,每个对象都包含一组Column
和Constraint
对象。这个对象结构将成为我们今后使用 Core 和 ORM 执行的大多数操作的中心。
我们可以对此结构进行的第一个有用的操作是发出 CREATE TABLE 语句,或者 DDL 到我们的 SQLite 数据库,以便我们可以从中插入和查询数据。我们已经拥有完成此操作所需的所有工具,通过在我们的MetaData
上调用MetaData.create_all()
方法,将目标数据库的Engine
传递给它:
>>> metadata_obj.create_all(engine) BEGIN (implicit) PRAGMA main.table_...info("user_account") ... PRAGMA main.table_...info("address") ... CREATE TABLE user_account ( id INTEGER NOT NULL, name VARCHAR(30), fullname VARCHAR, PRIMARY KEY (id) ) ... CREATE TABLE address ( id INTEGER NOT NULL, user_id INTEGER NOT NULL, email_address VARCHAR NOT NULL, PRIMARY KEY (id), FOREIGN KEY(user_id) REFERENCES user_account (id) ) ... COMMIT
以上的 DDL 创建过程包括一些 SQLite 特定的 PRAGMA 语句,用于在发出 CREATE 之前测试每个表的存在性。所有步骤也包含在 BEGIN/COMMIT 对中,以适应事务性 DDL。
create 进程还负责按正确顺序发出 CREATE 语句;以上,FOREIGN KEY 约束依赖于 user
表的存在,因此 address
表在第二创建。在更复杂的依赖情况下,FOREIGN KEY 约束也可能使用 ALTER 在表创建后进行应用。
MetaData
对象还具有一个 MetaData.drop_all()
方法,它将按照与发出 CREATE 相反的顺序发出 DROP 语句以删除模式元素。## 使用 ORM 声明式表单定义表元数据
在使用 ORM 时,声明 Table
元数据的过程通常与声明 映射 类的过程结合在一起。映射类是我们想要创建的任何 Python 类,然后它将具有链接到数据库表中列的属性。虽然有几种实现方式,但最常见的风格称为 声明式,它允许我们一次声明我们的用户定义类和 Table
元数据。
建立声明性基础
在使用 ORM 时,MetaData
集合仍然存在,但它本身与一个仅用于 ORM 的构造关联,通常称为 声明式基础。获取新的声明式基础的最简便方法是创建一个继承 SQLAlchemy DeclarativeBase
类的新类:
>>> from sqlalchemy.orm import DeclarativeBase >>> class Base(DeclarativeBase): ... pass
以上,Base
类就是我们将称为声明式基础的类。当我们创建新的类作为 Base
的子类时,并结合适当的类级指令,它们将在类创建时各自作为一个新的 ORM 映射类 建立,每个类通常(但不一定)引用一个特定的 Table
对象。
Declarative Base 指的是一个MetaData
集合,它会自动为我们创建,假设我们没有从外部提供。这个MetaData
集合可以通过DeclarativeBase.metadata
类级别属性访问。当我们创建新的映射类时,它们将分别引用此MetaData
集合内的一个Table
:
>>> Base.metadata MetaData()
Declarative Base 还指的是一个称为registry
的集合,它是 SQLAlchemy ORM 中的中央“映射器配置”单元。虽然很少直接访问,但该对象在映射器配置过程中是至关重要的,因为一组 ORM 映射类将通过此注册表相互协调。与MetaData
的情况一样,我们的 Declarative Base 也为我们创建了一个registry
(再次提供自己的registry
的选项),我们可以通过DeclarativeBase.registry
类变量访问它:
>>> Base.registry <sqlalchemy.orm.decl_api.registry object at 0x...> ```### 声明映射类 有了`Base`类的设立,我们现在可以根据新类`User`和`Address`定义`user_account`和`address`表的 ORM 映射类。我们下面展示了最现代化的 Declarative 形式,它是从[**PEP 484**](https://peps.python.org/pep-0484/)类型注解中驱动的,使用了一个特殊类型`Mapped`,它指示要映射为特定类型的属性: ```py >>> from typing import List >>> from typing import Optional >>> from sqlalchemy.orm import Mapped >>> from sqlalchemy.orm import mapped_column >>> from sqlalchemy.orm import relationship >>> class User(Base): ... __tablename__ = "user_account" ... ... id: Mapped[int] = mapped_column(primary_key=True) ... name: Mapped[str] = mapped_column(String(30)) ... fullname: Mapped[Optional[str]] ... ... addresses: Mapped[List["Address"]] = relationship(back_populates="user") ... ... def __repr__(self) -> str: ... return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})" >>> class Address(Base): ... __tablename__ = "address" ... ... id: Mapped[int] = mapped_column(primary_key=True) ... email_address: Mapped[str] ... user_id = mapped_column(ForeignKey("user_account.id")) ... ... user: Mapped[User] = relationship(back_populates="addresses") ... ... def __repr__(self) -> str: ... return f"Address(id={self.id!r}, email_address={self.email_address!r})"
上面的两个类User
和Address
现在被称为ORM 映射类,并且可以在 ORM 持久性和查询操作中使用,稍后将对这些类的详细信息进行描述:
- 每个类都指向一个
Table
对象,该对象是作为声明性映射过程的一部分生成的,并通过将字符串赋值给DeclarativeBase.__tablename__
属性命名。一旦类被创建,这个生成的Table
可以通过DeclarativeBase.__table__
属性进行访问。 - 如前所述,这种形式被称为声明性表配置。数种替代声明风格之一会让我们直接构建
Table
对象,并直接将其分配给DeclarativeBase.__table__
。这种风格被称为声明性与命令式表配置。 - 为了指示
Table
中的列,我们使用mapped_column()
构造,结合基于Mapped
类型的类型注释。此对象将生成应用于Table
构造的Column
对象。 - 对于简单数据类型且没有其他选项的列,我们可以单独指定
Mapped
类型注释,使用简单的 Python 类型如int
和str
表示Integer
和String
。在声明性映射过程中,如何解释 Python 类型的定制化是非常开放的;请参阅使用注释的声明性表(用于mapped_column()
的类型注释形式)和自定义类型映射部分了解背景知识。 - 根据
Optional[]
类型注释(或其等效形式,| None
或Union[, None]
)的存在,可以将列声明为“可空”或“非空”。还可以显式使用mapped_column.nullable
参数(不必与注释的可选性匹配)。 - 使用显式类型注释是完全可选的。我们也可以在没有注释的情况下使用
mapped_column()
。在使用这种形式时,我们会根据需要在每个mapped_column()
构造内使用更明确的类型对象,如Integer
和String
以及nullable=False
。 - 另外两个属性,
User.addresses
和Address.user
,定义了一种不同类型的属性,称为relationship()
,它具有与注释相似的配置样式。relationship()
构造在使用 ORM 相关对象中有更详细的讨论。 - 如果我们没有声明自己的
__init__()
方法,则会自动为类添加一个__init__()
方法。该方法的默认形式接受所有属性名称作为可选关键字参数:
>>> sandy = User(name="sandy", fullname="Sandy Cheeks")
- 要自动生成一个全功能的
__init__()
方法,既提供位置参数又提供具有默认关键字值的参数,可以使用在声明式数据类映射中引入的数据类功能。当然,始终可以选择使用显式的__init__()
方法。 - 添加
__repr__()
方法是为了获得可读的字符串输出;这些方法不需要存在的要求。与__init__()
一样,可以使用 dataclasses 功能自动生成__repr__()
方法。
另请参阅
ORM 映射风格 - 不同 ORM 配置风格的完整背景。
声明式映射 - 声明式类映射概述
使用mapped_column()
的声明式表 - 详细说明如何使用mapped_column()
和Mapped
来定义在使用声明式时要映射的Table
中的列。
从 ORM 映射向数据库发出 DDL
由于我们的 ORM 映射类引用包含在MetaData
集合中的Table
对象,所以根据声明式基类发出 DDL 与在 Emitting DDL to the Database 中描述的过程相同。在我们的情况下,我们已经在我们的 SQLite 数据库中生成了user
和address
表。如果我们之前没有这样做,我们可以自由地利用与我们的 ORM 声明基类相关联的MetaData
来做到这一点,方法是通过访问DeclarativeBase.metadata
属性中的集合,然后像以前一样使用MetaData.create_all()
。在这种情况下,会运行 PRAGMA 语句,但不会生成新表,因为已经发现它们已经存在:
>>> Base.metadata.create_all(engine) BEGIN (implicit) PRAGMA main.table_...info("user_account") ... PRAGMA main.table_...info("address") ... COMMIT ```## 表反射 为了完成与表元数据一起工作的部分,我们将说明在该部分开头提到的另一个操作,即**表反射**。表反射是指通过读取数据库的当前状态来生成`Table`和相关对象的过程。而在之前的部分中,我们一直在 Python 中声明`Table`对象,然后有选择地将 DDL 发出到数据库以生成这样的模式,反射过程将这两个步骤反向执行,从现有数据库开始,并生成用于表示该数据库中模式的 Python 数据结构。 提示 并非要求必须使用反射才能将 SQLAlchemy 与现有数据库一起使用。完全可以在 Python 中显式声明所有元数据,使其结构与现有数据库相对应,这是很典型的。元数据结构也不必包含表、列或其他在本地应用程序中不需要的预先存在数据库中的约束和构造。 作为反射的示例,我们将创建一个新的`Table`对象,该对象表示我们在本文档的前几节中手动创建的`some_table`对象。这样做的方式有很多种,但最基本的方式是构建一个`Table`对象,给定表的名称和它将属于的`MetaData`集合,然后不是指示单独的`Column`和`Constraint`对象,而是传递目标`Engine`使用`Table.autoload_with`参数: ```py >>> some_table = Table("some_table", metadata_obj, autoload_with=engine) BEGIN (implicit) PRAGMA main.table_...info("some_table") [raw sql] () SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE name = ? AND type in ('table', 'view') [raw sql] ('some_table',) PRAGMA main.foreign_key_list("some_table") ... PRAGMA main.index_list("some_table") ... ROLLBACK
在这个过程的结尾,some_table
对象现在包含了表中存在的Column
对象的信息,该对象可与我们明确声明的Table
完全相同的方式使用:
>>> some_table Table('some_table', MetaData(), Column('x', INTEGER(), table=<some_table>), Column('y', INTEGER(), table=<some_table>), schema=None)
另请参阅
了解有关表和模式反射的更多信息,请参阅反射数据库对象。
对于 ORM 相关的表反射变体,在使用反射表声明映射一节中包含了可用选项的概述。
下一步
现在我们有一个 SQLite 数据库准备好使用,其中有两个表存在,并且我们可以使用Connection
和/或 ORM Session
通过 Core 和 ORM 表导向的构造与这些表进行交互。在接下来的章节中,我们将说明如何使用这些结构创建、操作和选择数据。
SqlAlchemy 2.0 中文文档(一)(4)https://developer.aliyun.com/article/1563204