# Fluent

Fluent 是服务于 Swift 的 [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping) 框架，它利用 Swift 的强类型特性为你的数据库操作提供简易使用的接口。使用 Fluent 的核心是创建用于表示数据库中数据结构的模型，然后通过这些模型来执行创建、读取、更新和删除等操作，而不必编写原始的 SQL 查询语句。

## 配置

当在终端使用 `vapor new` 命令来创建项目时，对于包含询问 Fluent 的问答选项，请选择 'Yes'，并选择要使用的数据库驱动程序，之后将自动生成依赖项添加到你的新项目以及配置示例代码。

### 现有项目

如果你想为现有项目添加 Fluent，你需要在 [package](../getting-started/spm.zh.md) 中添加两个依赖项：

- [vapor/fluent](https://github.com/vapor/fluent)@4.0.0
- 你选择的一个（或多个）Fluent 驱动程序

```swift
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/fluent-<db>-driver.git", from: <version>),
```

```swift
.target(name: "App", dependencies: [
    .product(name: "Fluent", package: "fluent"),
    .product(name: "Fluent<db>Driver", package: "fluent-<db>-driver"),
    .product(name: "Vapor", package: "vapor"),
]),
```

一旦这些包被添加为依赖项，你就可以在 `configure.swift` 中使用 `app.databases` 配置你的数据库。

```swift
import Fluent
import Fluent<db>Driver

app.databases.use(<db config>, as: <identifier>)
```

下面的每个 Fluent 驱动程序都有更具体的配置说明。

### 驱动

Fluent 目前有四个官方支持的驱动程序。你可以在 GitHub 上搜索标签 [`fluent-driver`](https://github.com/topics/fluent-driver) 来获取官方和第三方 Fluent 数据库驱动程序的完整列表。

#### PostgreSQL

PostgreSQL 是一个开源的、符合标准的 SQL 数据库。 它很容易在大多数云托管提供商上进行配置。这是 Fluent **推荐**的数据库驱动程序。

要使用 PostgreSQL，请将以下依赖项添加到 package 中。

```swift
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0")
```

```swift
.product(name: "FluentPostgresDriver", package: "fluent-postgres-driver")
```

添加依赖项后，在 `configure.swift` 中使用 `app.databases.use` 配置数据库的凭证。 

```swift
import Fluent
import FluentPostgresDriver

app.databases.use(
    .postgres(
        configuration: .init(
            hostname: "localhost",
            username: "vapor",
            password: "vapor",
            database: "vapor",
            tls: .disable
        )
    ),
    as: .psql
)
```

还可以从数据库连接字符串解析凭证。

```swift
try app.databases.use(.postgres(url: "<connection string>"), as: .psql)
```

#### SQLite

SQLite 是一种开源的，嵌入式 SQL 数据库。它的简单性使其成为原型设计和测试的理想选择。

要使用 SQLite，请将以下依赖项添加到 package 中。

```swift
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0")
```

```swift
.product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver")
```

添加依赖项后，在 `configure.swift` 中使用 `app.databases.use` 配置数据库的凭证。 

```swift
import Fluent
import FluentSQLiteDriver

app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)
```

你还可以配置 SQLite 将数据库临时存储在内存中。

```swift
app.databases.use(.sqlite(.memory), as: .sqlite)
```

如果使用内存中的数据库，请确保使用 `--auto-migrate` 将 Fluent 设置为自动迁移，或者在添加迁移后运行 `app.autoMigrate()`。

```swift
app.migrations.add(CreateTodo())
try app.autoMigrate().wait()
// or
try await app.autoMigrate()
```

!!! tip "建议"
    SQLite 配置自动对所有创建的连接启用外键约束，但不会更改数据库本身的外键配置。直接删除数据库中的记录可能会违反外键约束和触发器。

#### MySQL

MySQL 是一种流行的开源 SQL 数据库。许多云托管服务提供商都提供它。该驱动程序还支持 MariaDB。

要使用 MySQL，请将以下依赖项添加到你的 package 中。

```swift
.package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0")
```

```swift
.product(name: "FluentMySQLDriver", package: "fluent-mysql-driver")
```

添加依赖项后，在 `configure.swift` 中使用 `app.databases.use` 配置数据库的凭证。

```swift
import Fluent
import FluentMySQLDriver

app.databases.use(.mysql(hostname: "localhost", username: "vapor", password: "vapor", database: "vapor"), as: .mysql)
```

你还可以从数据库连接字符串中解析凭证。

```swift
try app.databases.use(.mysql(url: "<connection string>"), as: .mysql)
```

要配置不涉及 SSL 证书的本地连接，你应该禁用证书验证。例如，如果连接到 Docker 中的 MySQL 8 数据库，你可能需要这样做。

```swift
var tls = TLSConfiguration.makeClientConfiguration()
tls.certificateVerification = .none
    
app.databases.use(.mysql(
    hostname: "localhost",
    username: "vapor",
    password: "vapor",
    database: "vapor",
    tlsConfiguration: tls
), as: .mysql)
```

!!! warning "警告"
    不要在生产中禁用证书验证。你应该向 `TLSConfiguration` 提供一个证书以进行验证。

#### MongoDB

MongoDB 是一种流行的无模式 NoSQL 数据库，专为程序员设计。该驱动程序支持 3.4 及更高版本的所有云托管提供商和自托管安装。

!!! note "注意" 
    该驱动程序由一个名为 [MongoKitten](https://github.com/OpenKitten/MongoKitten) 的社区创建和维护的 MongoDB 客户端提供支持。MongoDB 维护着一个官方客户端，[mongo-swift-driver](https://github.com/mongodb/mongo-swift-driver) 以及 Vapor 集成的 [mongodb-vapor](https://github.com/mongodb/mongodb-vapor)。

要使用 MongoDB，请将以下依赖项添加到你的 package 中。

```swift
.package(url: "https://github.com/vapor/fluent-mongo-driver.git", from: "1.0.0"),
```

```swift
.product(name: "FluentMongoDriver", package: "fluent-mongo-driver")
```

添加依赖项后，在 `configure.swift` 中使用 `app.databases.use` 配置数据库的凭证。

要进行连接，请传递标准 MongoDB [连接 URI 格式](https://docs.mongodb.com/master/reference/connection-string/index.html)的连接字符串。


```swift
import Fluent
import FluentMongoDriver

try app.databases.use(.mongo(connectionString: "<connection string>"), as: .mongo)
```

## Models

模型表示数据库中固定的数据结构，如表或集合。模型有一个或多个存储可编码值的字段。所有模型都有一个唯一的标识符。属性包装器用于表示标识符和字段以及后面提到的更复杂的映射。看看下面这个代表星系的模型。

```swift
final class Galaxy: Model {
    // 表或集合名。
    static let schema = "galaxies"

    // 星系唯一标识符。
    @ID(key: .id)
    var id: UUID?

    // 星系名称。
    @Field(key: "name")
    var name: String

    // 创建一个空的星系。
    init() { }

    // 创建一个新的星系并设置所有属性。
    init(id: UUID? = nil, name: String) {
        self.id = id
        self.name = name
    }
}
```

要创建一个新的模型，请创建一个遵循 `Model` 协议的类。

!!! tip "建议" 
    建议将模型类标记为 `final`，以提高性能并简化一致性要求。

`Model` 协议第一个要求是静态字符串 `schema`。

```swift
static let schema = "galaxies"
```

此属性告诉 Fluent 模型对应于哪个表或集合。这可以是数据库中已经存在的表，也可以是你将通过 [migration](#migrations) 创建的表。schema 通常是 `snake_case` 复数形式.

### Identifier

下一个要求是一个名为 `id` 的标识符字段。

```swift
@ID(key: .id)
var id: UUID?
```

该字段必须使用 `@ID` 属性包装器。Fluent 建议使用 `UUID` 和 特殊 `.id` 字段键，因为它兼容 Fluent 的所有驱动程序。

如果要使用自定义 ID 键或类型， 请使用 [`@ID(custom:)`](model.zh.md#custom-identifier) 重载。

### Fields

添加标识符之后，你可以添加任意多的字段来存储额外的信息。在本例中，唯一的附加字段是星系的名称。

```swift
@Field(key: "name")
var name: String
```

对于简单字段，使用 `@Field` 属性包装器。 与 `@ID` 一样，`key` 参数指定数据库中字段的名称。这在数据库字段命名约定可能与 Swift 不同的情况下特别有用，例如，使用 `snake_case` 而不是 `camelCase`。

接下来，所有模型都需要一个空的 init。这允许 Fluent 创建模型的新实例。

```swift
init() { }
```

最后，你可以为模型添加一个方便的 init 来设置其所有属性。

```swift
init(id: UUID? = nil, name: String) {
    self.id = id
    self.name = name
}
```

如果你向模型添加新属性，则使用便捷的 inits 尤其有用，因为如果 init 方法发生更改，你可能会收到编译时错误。

## Migrations

如果数据库使用预定义的模式，如 SQL 数据库，则需要进行迁移，以便为模型准备数据库。迁移对于用数据填充数据库也很有用。要创建一个迁移，定义一个符合 `migration` 或 `AsyncMigration` 协议的新类型。看看下面对之前定义的 `Galaxy` 模型的迁移。

```swift
struct CreateGalaxy: AsyncMigration {
    // 为存储 Galaxy 模型准备数据库。
    func prepare(on database: Database) async throws {
        try await database.schema("galaxies")
            .id()
            .field("name", .string)
            .create()
    }

    // 可选地恢复 prepare 方法中所做的更改。
    func revert(on database: Database) async throws {
        try await database.schema("galaxies").delete()
    }
}
```

该 `prepare` 方法用于准备数据库以存储 `Galaxy` 模型。

### Schema

在此方法中，`database.schema(_:)` 用来创建一个新的 `schembuilder`。然后，在调用 `create()` 创建模式之前，将一个或多个 `字段` 添加到构建器中。

添加到构建器的每个字段都有一个名称、类型和可选约束。

```swift
field(<name>, <type>, <optional constraints>)
```

使用 Fluent 推荐的默认值，有一个方便的 `id()` 方法可以添加 `@ID` 属性。

恢复迁移会撤消在 prepare 方法中所做的任何更改。在这种情况下，这意味着删除 Galaxy 的模式。

一旦定义了迁移，你必须将迁移添加到 `configure.swift` 中的 `app.migrations` 来告知 Fluent 。

```swift
app.migrations.add(CreateGalaxy())
```

### Migrate

要运行迁移，在命令行中调用 `swift run App migrate` 或者添加 `migrate` 参数到 Xcode 的运行方案中。


```
$ swift run App migrate
Migrate Command: Prepare
The following migration(s) will be prepared:
+ CreateGalaxy on default
Would you like to continue?
y/n> y
Migration successful
```

## Querying

现在你已经成功地创建了一个模型并迁移了你的数据库，你可以进行第一次查询了。

### All

看看下面的路由，它将返回一个包含数据库中所有星系的数组。

```swift
app.get("galaxies") { req async throws in
    try await Galaxy.query(on: req.db).all()
}
```

为了在路由闭包中直接返回 Galaxy，添加遵循 `Content` 协议

```swift
final class Galaxy: Model, Content {
    ...
}
```

`Galaxy.query` 用于为模型创建新的查询构建器。`req.db` 是对你的应用程序的默认数据库的引用。 最后， `all()` 返回存储在数据库中的所有模型。

如果你编译并运行项目并请求 `GET /galaxies`， 你会看到返回一个空数组。 让我们添加一个创建新星系的路由。

### Create


根据 RESTful 约定，使用 `POST /galaxies` 端点创建新星系。 由于模型是可编码的，因此你可以直接从请求正文中解码星系。

```swift
app.post("galaxies") { req -> EventLoopFuture<Galaxy> in
    let galaxy = try req.content.decode(Galaxy.self)
    return galaxy.create(on: req.db)
        .map { galaxy }
}
```

!!! seealso "也可以看看"
    有关解码请求正文的更多信息，请参阅 [内容 → 概述](../basics/content.zh.md)。

一旦有了模型的实例，调用 `create(on:)` 就会将模型保存到数据库中。这将返回一个 `EventLoopFuture<Void>` 表示保存已完成的信号。保存完成后，使用 `map` 返回新创建的模型。

如果你正在使用 `async/await` 你可以这样编写代码：

```swift
app.post("galaxies") { req async throws -> Galaxy in
    let galaxy = try req.content.decode(Galaxy.self)
    try await galaxy.create(on: req.db)
    return galaxy
}
```

在这种情况下，异步版本不会返回任何内容，但会在保存完成后返回。

构建并运行项目并发送以下请求。

```http
POST /galaxies HTTP/1.1
content-length: 21
content-type: application/json

{
    "name": "Milky Way"
}
```

你应该以标识符作为响应来取回创建的模型。

```json
{
    "id": ...,
    "name": "Milky Way"
}
```

现在，如果你 `GET /galaxies` 再次查询，你应该会看到数组中返回了新创建的星系。


## Relations

没有恒星的星系算什么! 让我们通过在 `Galaxy` 和新的 `Star` 模型之间添加一对多关系来快速了解一下 `Fluent` 强大的关系特性。

```swift
final class Star: Model, Content {
    // 表或集合名。
    static let schema = "stars"

    // Star 唯一标识符。
    @ID(key: .id)
    var id: UUID?

    // Star 名称
    @Field(key: "name")
    var name: String

    // 引用这颗恒星所在的星系。
    @Parent(key: "galaxy_id")
    var galaxy: Galaxy

    // 创建一个空的 Star。
    init() { }

    // 创建一个新的 Star，设置所有属性。
    init(id: UUID? = nil, name: String, galaxyID: UUID) {
        self.id = id
        self.name = name
        self.$galaxy.id = galaxyID
    }
}
```

### Parent

除了 `@Parent` 新字段类型，新的 `Star` 模型与 `Galaxy` 非常相似。

```swift
@Parent(key: "galaxy_id")
var galaxy: Galaxy
```

parent 属性是存储另一个模型标识符的字段。持有引用的模型称为`子`，被引用的模型称为`父`。这种类型的关系也称为“一对多”。该 `key` 参数指定了用于在数据库中存储父键的字段名称。

在 init 方法中，父标识符使用 `$galaxy`。

```swift
self.$galaxy.id = galaxyID
```

通过为父属性的名称添加前缀 `$`，你可以访问底层属性包装器。这是访问 `@Field` 存储实际标识符值的内部所必需的。

!!! seealso "也可以看看"
    查看 Swift Evolution 关于属性包装器的提案以获得更多信息:[[SE-0258] Property Wrappers](https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md)

接下来，创建一个迁移以准备数据库来处理 `Star`。

```swift
struct CreateStar: AsyncMigration {
    // 为存储 Star 模型准备数据库。
    func prepare(on database: Database) async throws {
        try await database.schema("stars")
            .id()
            .field("name", .string)
            .field("galaxy_id", .uuid, .references("galaxies", "id"))
            .create()
    }

    // 可选地恢复 prepare 方法中所做的更改。
    func revert(on database: Database) async throws {
        try await database.schema("stars").delete()
    }
}
```

这与星系的迁移基本相同，除了存储父星系标识符的额外字段。

```swift
field("galaxy_id", .uuid, .references("galaxies", "id"))
```

该字段指定了一个可选的约束，告诉数据库该字段的值引用了 “galaxies” 模式中的字段 “id”。这也称为外键，有助于确保数据完整性。

一旦迁移创建完成，将其添加到 `app.migrations` 中的 `CreateGalaxy`之后。

```swift
app.migrations.add(CreateGalaxy())
app.migrations.add(CreateStar())
```

因为迁移是按顺序运行的，而且 `CreateStar` 引用了星系模式，所以顺序是很重要的。最后，[运行迁移](#migrate)准备数据库。

添加创建新恒星的路由。

```swift
app.post("stars") { req async throws -> Star in
    let star = try req.content.decode(Star.self)
    try await star.create(on: req.db)
    return star
}
```
使用下面的HTTP请求创建一个引用之前创建的星系的新星。

```http
POST /stars HTTP/1.1
content-length: 36
content-type: application/json

{
    "name": "Sun",
    "galaxy": {
        "id": ...
    }
}
```

你应该看到新创建的星星返回一个惟一标识符。

```json
{
    "id": ...,
    "name": "Sun",
    "galaxy": {
        "id": ...
    }
}
```

### Children

现在让我们看看如何利用 Fluent 的预加载功能自动返回 `GET /galaxies` 路由中的星系恒星。将以下属性添加到 `Galaxy`模型中。

```swift
// 这个星系的所有恒星。
@Children(for: \.$galaxy)
var stars: [Star]
```

`@Children` 属性包装器与 `@Parent`相反。它采用子 `@Parent` 字段的键路径作为 `for`参数。它的值是一个子模型数组，因为可能存在零个或多个子模型。星系的迁移不需要改变，因为这种关系所需的所有信息都存储在`恒星`上。


### Eager Load

现在关系已经完成，你可以在查询构建器上使用 `with` 方法来自动获取和序列化星系-星型关系。

```swift
app.get("galaxies") { req in
    try await Galaxy.query(on: req.db).with(\.$stars).all()
}
```

一个指向 `@Children` 关系的键路径被传递给 `with`，告诉 Fluent 在所有结果模型中自动加载这个关系。创建并运行另一个请求到 `GET /galaxies`。你现在应该看到响应中自动包含星星。

```json
[
    {
        "id": ...,
        "name": "Milky Way",
        "stars": [
            {
                "id": ...,
                "name": "Sun",
                "galaxy": {
                    "id": ...
                }
            }
        ]
    }
]
```

## 查询日志

Fluent 驱动程序在日志级别为 debug 时会记录生成的 SQL 语句。一些驱动程序，如 FluentPostgreSQL，允许在配置数据库时进行配置。

要设置日志级别，请在 **configure.swift**（或你设置应用程序的位置）文件中添加：

```swift
app.logger.logLevel = .debug
```

这会将日志级别设置为 debug。下次构建和运行应用程序时，Fluent 生成的 SQL 语句会在控制台输出。

## 下一步

祝贺你创建了第一个模型和迁移，并执行了基本的创建和读取操作。要了解更多关于所有这些特性的深入信息，请查看 Fluent 指南中的相应部分。