# SQL（关系型）数据库 { #sql-relational-databases }

**FastAPI** 并不要求你使用 SQL（关系型）数据库。你可以使用你想用的**任何数据库**。

这里，我们来看一个使用 [SQLModel](https://sqlmodel.tiangolo.com/) 的示例。

**SQLModel** 基于 [SQLAlchemy](https://www.sqlalchemy.org/) 和 Pydantic 构建。它由 **FastAPI** 的同一作者制作，旨在完美匹配需要使用**SQL 数据库**的 FastAPI 应用程序。

/// tip | 提示

你可以使用任意其他你想要的 SQL 或 NoSQL 数据库库（在某些情况下称为 <abbr title="Object Relational Mapper - 对象关系映射器: 一个术语，用来指代一种库，其中某些类表示 SQL 表，这些类的实例表示这些表中的行">"ORMs"</abbr>），FastAPI 不会强迫你使用任何东西。😎

///

由于 SQLModel 基于 SQLAlchemy，因此你可以轻松使用任何由 SQLAlchemy **支持的数据库**（这也让它们被 SQLModel 支持），例如：

* PostgreSQL
* MySQL
* SQLite
* Oracle
* Microsoft SQL Server 等

在这个示例中，我们将使用 **SQLite**，因为它使用单个文件，并且 Python 对其有集成支持。因此，你可以直接复制这个示例并运行。

之后，对于你的生产应用程序，你可能会想要使用像 **PostgreSQL** 这样的数据库服务器。

/// tip | 提示

有一个使用 **FastAPI** 和 **PostgreSQL** 的官方项目生成器，其中包括了前端和更多工具： [https://github.com/fastapi/full-stack-fastapi-template](https://github.com/fastapi/full-stack-fastapi-template)

///

这是一个非常简单和简短的教程。如果你想了解一般的数据库、SQL 或更高级的功能，请查看 [SQLModel 文档](https://sqlmodel.tiangolo.com/)。

## 安装 `SQLModel` { #install-sqlmodel }

首先，确保你创建并激活了[虚拟环境](../virtual-environments.md)，然后安装 `sqlmodel`：

<div class="termy">

```console
$ pip install sqlmodel
---> 100%
```

</div>

## 创建含有单一模型的应用 { #create-the-app-with-a-single-model }

我们先创建应用的最简单的第一个版本，只有一个 **SQLModel** 模型。

稍后我们将通过下面的**多个模型**提高其安全性和多功能性。🤓

### 创建模型 { #create-models }

导入 `SQLModel` 并创建一个数据库模型：

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *}

`Hero` 类与 Pydantic 模型非常相似（实际上，从底层来看，它确实就是一个 Pydantic 模型）。

有一些区别：

* `table=True` 会告诉 SQLModel 这是一个*表模型*，它应该表示 SQL 数据库中的一个**表**，而不仅仅是一个*数据模型*（就像其他常规的 Pydantic 类一样）。

* `Field(primary_key=True)` 会告诉 SQLModel `id` 是 SQL 数据库中的**主键**（你可以在 SQLModel 文档中了解更多关于 SQL 主键的信息）。

    **注意：** 我们为主键字段使用 `int | None`，这样在 Python 代码中我们可以在没有 `id`（`id=None`）的情况下创建对象，并假定数据库在保存时会生成它。SQLModel 会理解数据库会提供 `id`，并在数据库模式中将该列定义为非空的 `INTEGER`。详见 [SQLModel 关于主键的文档](https://sqlmodel.tiangolo.com/tutorial/create-db-and-table/#primary-key-id)。

* `Field(index=True)` 会告诉 SQLModel 应该为此列创建一个 **SQL 索引**，这样在读取按此列过滤的数据时，程序能在数据库中进行更快的查找。

    SQLModel 会知道声明为 `str` 的内容将是类型为 `TEXT`（或 `VARCHAR`，具体取决于数据库）的 SQL 列。

### 创建引擎（Engine） { #create-an-engine }

SQLModel 的 `engine`（实际上它是一个 SQLAlchemy 的 `engine`）是用来与数据库**保持连接**的。

你只需构建**一个 `engine` 对象**，让你的所有代码连接到同一个数据库。

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *}

使用 `check_same_thread=False` 可以让 FastAPI 在不同线程中使用同一个 SQLite 数据库。这很有必要，因为**单个请求**可能会使用**多个线程**（例如在依赖项中）。

不用担心，我们会按照代码结构确保**每个请求使用一个单独的 SQLModel 会话（session）**，这实际上就是 `check_same_thread` 想要实现的。

### 创建表 { #create-the-tables }

然后，我们来添加一个函数，使用 `SQLModel.metadata.create_all(engine)` 为所有*表模型***创建表**。

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *}

### 创建会话（Session）依赖项 { #create-a-session-dependency }

**`Session`** 会存储**内存中的对象**并跟踪数据中所需更改的内容，然后它**使用 `engine`** 与数据库进行通信。

我们会使用 `yield` 创建一个 FastAPI **依赖项**，为每个请求提供一个新的 `Session`。这确保我们每个请求使用一个单独的会话。🤓

然后我们创建一个 `Annotated` 的依赖项 `SessionDep` 来简化其他也会用到此依赖的代码。

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30]  hl[25:27,30] *}

### 在启动时创建数据库表 { #create-database-tables-on-startup }

我们会在应用程序启动时创建数据库表。

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *}

此处，在应用程序启动事件中，我们创建了表。

在生产环境中，你可能会使用一个在启动应用程序之前运行的迁移脚本。🤓

/// tip | 提示

SQLModel 将会拥有封装 Alembic 的迁移工具，但目前你可以直接使用 [Alembic](https://alembic.sqlalchemy.org/en/latest/)。

///

### 创建 Hero { #create-a-hero }

因为每个 SQLModel 模型同时也是一个 Pydantic 模型，所以你可以在与 Pydantic 模型相同的**类型注解**中使用它。

例如，如果你声明一个类型为 `Hero` 的参数，它将从 **JSON 主体**中读取数据。

同样，你可以将其声明为函数的**返回类型**，然后数据的结构就会显示在自动生成的 API 文档界面中。

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *}

这里，我们使用 `SessionDep` 依赖项（一个 `Session`）将新的 `Hero` 添加到 `Session` 实例中，提交更改到数据库，刷新 `hero` 中的数据，并返回它。

### 读取 Hero { #read-heroes }

我们可以使用 `select()` 从数据库中**读取** `Hero`，并利用 `limit` 和 `offset` 来对结果进行分页。

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *}

### 读取单个 Hero { #read-one-hero }

我们可以**读取**单个 `Hero`。

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *}

### 删除单个 Hero { #delete-a-hero }

我们也可以**删除**一个 `Hero`。

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *}

### 运行应用 { #run-the-app }

你可以运行这个应用：

<div class="termy">

```console
$ fastapi dev

<span style="color: green;">INFO</span>:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```

</div>

然后在 `/docs` UI 中，你能够看到 **FastAPI** 会用这些**模型**来**记录** API，并且还会用它们来**序列化**和**验证**数据。

<div class="screenshot">
<img src="/img/tutorial/sql-databases/image01.png">
</div>

## 使用多个模型更新应用 { #update-the-app-with-multiple-models }

现在让我们稍微**重构**一下这个应用，以提高**安全性**和**多功能性**。

如果你查看之前的应用程序，你可以在 UI 界面中看到，到目前为止，它允许客户端决定要创建的 `Hero` 的 `id`。😱

我们不应该允许这样做，因为他们可能会覆盖我们在数据库中已经分配的 `id`。决定 `id` 的行为应该由**后端**或**数据库**来完成，**而非客户端**。

此外，我们为 hero 创建了一个 `secret_name`，但到目前为止，我们在各处都返回了它，这就不太**秘密**了……😅

我们将通过添加一些**额外的模型**来解决这些问题，而 SQLModel 将在这里大放异彩。✨

### 创建多个模型 { #create-multiple-models }

在 **SQLModel** 中，任何含有 `table=True` 属性的模型类都是一个**表模型**。

任何不含有 `table=True` 属性的模型类都是**数据模型**，这些实际上只是 Pydantic 模型（附带一些小的额外功能）。🤓

有了 SQLModel，我们就可以利用**继承**来在所有情况下**避免重复**所有字段。

#### `HeroBase` - 基类 { #herobase-the-base-class }

我们从一个 `HeroBase` 模型开始，该模型具有所有模型**共享的字段**：

* `name`
* `age`

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *}

#### `Hero` - *表模型* { #hero-the-table-model }

接下来，我们创建 `Hero`，实际的*表模型*，并添加那些不总是在其他模型中的**额外字段**：

* `id`
* `secret_name`

因为 `Hero` 继承自 `HeroBase`，所以它**也**包含了在 `HeroBase` 中声明过的**字段**。因此 `Hero` 的所有字段为：

* `id`
* `name`
* `age`
* `secret_name`

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *}

#### `HeroPublic` - 公共*数据模型* { #heropublic-the-public-data-model }

接下来，我们创建一个 `HeroPublic` 模型，这是将**返回**给 API 客户端的模型。

它包含与 `HeroBase` 相同的字段，因此不会包括 `secret_name`。

终于，我们英雄的身份得到了保护！🥷

它还重新声明了 `id: int`。这样我们便与 API 客户端建立了一种**约定**，使他们始终可以期待 `id` 存在并且是一个整数 `int`（永远不会是 `None`）。

/// tip | 提示

确保返回模型始终提供一个值并且始终是 `int`（而不是 `None`）对 API 客户端非常有用，他们可以在这种确定性下编写更简单的代码。

此外，**自动生成的客户端**将拥有更简洁的接口，这样与你的 API 交互的开发者就能更轻松地使用你的 API。😎

///

`HeroPublic` 中的所有字段都与 `HeroBase` 中的相同，其中 `id` 声明为 `int`（不是 `None`）：

* `id`
* `name`
* `age`

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *}

#### `HeroCreate` - 用于创建 hero 的*数据模型* { #herocreate-the-data-model-to-create-a-hero }

现在我们创建一个 `HeroCreate` 模型，这是用于**验证**客户端数据的模型。

它不仅拥有与 `HeroBase` 相同的字段，还有 `secret_name`。

现在，当客户端**创建一个新的 hero** 时，他们会发送 `secret_name`，它会被存储到数据库中，但这些 `secret_name` 不会通过 API 返回给客户端。

/// tip | 提示

这应当是**密码**被处理的方式：接收密码，但不要通过 API 返回它们。

在存储密码之前，你还应该对密码的值进行**哈希**处理，**绝不要以明文形式存储它们**。

///

`HeroCreate` 的字段包括：

* `name`
* `age`
* `secret_name`

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *}

#### `HeroUpdate` - 用于更新 hero 的*数据模型* { #heroupdate-the-data-model-to-update-a-hero }

在之前的应用程序中，我们没有办法**更新 hero**，但现在有了**多个模型**，我们便能做到这一点了。🎉

`HeroUpdate` *数据模型*有些特殊，它包含创建新 hero 所需的**所有相同字段**，但所有字段都是**可选的**（它们都有默认值）。这样，当你更新一个 hero 时，你可以只发送你想要更新的字段。

因为所有**字段实际上**都发生了**变化**（类型现在包括 `None`，并且它们现在有一个默认值 `None`），我们需要**重新声明**它们。

我们并不真的需要从 `HeroBase` 继承，因为我们会重新声明所有字段。我会让它继承只是为了保持一致，但这并不必要。这更多是个人喜好的问题。🤷

`HeroUpdate` 的字段包括：

* `name`
* `age`
* `secret_name`

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *}

### 使用 `HeroCreate` 创建并返回 `HeroPublic` { #create-with-herocreate-and-return-a-heropublic }

既然我们有了**多个模型**，我们就可以对使用它们的应用程序部分进行更新。

我们在请求中接收到一个 `HeroCreate` *数据模型*，然后从中创建一个 `Hero` *表模型*。

这个新的*表模型* `Hero` 会包含客户端发送的字段，以及一个由数据库生成的 `id`。

然后我们将与函数中相同的*表模型* `Hero` 原样返回。但是由于我们使用 `HeroPublic` *数据模型*声明了 `response_model`，**FastAPI** 会使用 `HeroPublic` 来验证和序列化数据。

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *}

/// tip | 提示

现在我们使用 `response_model=HeroPublic` 来代替**返回类型注解** `-> HeroPublic`，因为我们返回的值实际上并不是 `HeroPublic`。

如果我们声明了 `-> HeroPublic`，你的编辑器和代码检查工具会（理所应当地）抱怨你返回了一个 `Hero` 而不是一个 `HeroPublic`。

通过 `response_model` 的声明，我们让 **FastAPI** 按照它自己的方式处理，而不会干扰类型注解以及编辑器和其他工具提供的帮助。

///

### 使用 `HeroPublic` 读取 Hero { #read-heroes-with-heropublic }

我们可以像之前一样**读取** `Hero`，同样，使用 `response_model=list[HeroPublic]` 确保正确地验证和序列化数据。

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *}

### 使用 `HeroPublic` 读取单个 Hero { #read-one-hero-with-heropublic }

我们可以**读取**单个 hero：

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *}

### 使用 `HeroUpdate` 更新单个 Hero { #update-a-hero-with-heroupdate }

我们可以**更新**单个 hero。为此，我们会使用 HTTP 的 `PATCH` 操作。

在代码中，我们会得到一个 `dict`，其中包含客户端发送的所有数据，**只有客户端发送的数据**，并排除了任何一个仅仅作为默认值存在的值。为此，我们使用 `exclude_unset=True`。这是最主要的技巧。🪄

然后我们会使用 `hero_db.sqlmodel_update(hero_data)`，来利用 `hero_data` 的数据更新 `hero_db`。

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *}

### （再次）删除单个 Hero { #delete-a-hero-again }

**删除**一个 hero 基本保持不变。

我们不会满足在这一部分中重构一切的愿望。😅

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *}

### （再次）运行应用 { #run-the-app-again }

你可以再运行一次应用程序：

<div class="termy">

```console
$ fastapi dev

<span style="color: green;">INFO</span>:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```

</div>

如果你进入 `/docs` API UI，你会看到它现在已经更新，并且在创建 hero 时，它不会再期望从客户端接收 `id` 数据等。

<div class="screenshot">
<img src="/img/tutorial/sql-databases/image02.png">
</div>

## 总结 { #recap }

你可以使用 [**SQLModel**](https://sqlmodel.tiangolo.com/) 与 SQL 数据库进行交互，并通过*数据模型*和*表模型*简化代码。

你可以在 **SQLModel** 文档中学习到更多内容，其中有一个更详细的[将 SQLModel 与 **FastAPI** 一起使用的迷你教程](https://sqlmodel.tiangolo.com/tutorial/fastapi/)。🚀
