ORM 与原生 SQL 的取舍
ORM(Object-Relational Mapping,对象关系映射)是一种让开发者用面向对象的方式操作关系型数据库的技术。你定义 Swift 结构体/类来表示数据库表,ORM 框架负责将方法调用转换为 SQL 语句并执行。
Fluent 是 Vapor 的官方 ORM,支持 PostgreSQL、MySQL、SQLite 多种数据库。它的核心价值在于:类型安全(字段名拼写错误在编译期报错)、跨数据库抽象(切换数据库无需改业务代码)、Migration 版本管理(数据库结构变更有历史记录)。
| 维度 | Fluent ORM | 原生 SQL |
|---|---|---|
| 类型安全 | 编译期检查字段 | 运行时才报错 |
| 可读性 | Swift 风格,直观 | 字符串拼接,易出错 |
| 性能 | 轻微抽象损耗 | 最优,直接执行 |
| 复杂查询 | 某些场景较繁琐 | 灵活,无限制 |
| 数据库迁移 | Migration 体系管理 | 手动维护 SQL 文件 |
| 跨数据库 | 切换驱动即可 | 需改 SQL 方言 |
Fluent 也支持通过 db.raw() 执行原生 SQL,两种方式可以混用——让 ORM 处理常规 CRUD,让原生 SQL 处理复杂报表查询。
核心概念词典
Model
Fluent 的核心协议,实现它的类型代表数据库中的一张表。每个字段用属性包装器(
@ID、@Field、@Parent等)标注,Fluent 据此生成 SQL 列映射。Model 必须是引用类型(class)。Migration
数据库迁移脚本,描述数据库结构的创建或变更。实现
prepare(on:) 方法(创建/修改表)和 revert(on:) 方法(回滚)。Fluent 追踪已执行的 Migration,保证每条只执行一次,是数据库版本管理的基础。QueryBuilder
链式查询构建器,通过
Model.query(on: db) 创建,支持 .filter()、.sort()、.range()、.join()、.with()(预加载关联)等方法组合,最终调用 .all()、.first()、.count() 等执行查询。@Field / @ID
属性包装器,
@ID 标注主键(自动生成 UUID 或自增 Int),@Field(key:) 标注普通列并指定数据库列名。列名用 FieldKey 枚举而非字符串字面量,避免拼写错误。@Parent / @Children / @Siblings
关系属性包装器:
@Parent 表示"属于"(外键),@Children 表示"拥有多个"(一对多的"多"端),@Siblings 表示多对多关系(通过中间表)。这三者是 Fluent 关联查询的基础。定义 Model 与 Migration
以博客系统为例,定义 User 和 Post 两个模型,演示一对多关系。规范做法是把字段的 FieldKey 定义为枚举扩展,避免字符串散落在代码各处。
// Sources/App/Models/User.swift
import Fluent
import Vapor
final class User: Model, Content {
static let schema = "users" // 数据库表名
@ID(format: .uuid)
var id: UUID?
@Field(key: FieldKeys.name)
var name: String
@Field(key: FieldKeys.email)
var email: String
@Field(key: FieldKeys.passwordHash)
var passwordHash: String
@Timestamp(key: FieldKeys.createdAt, on: .create)
var createdAt: Date?
// 一对多:一个 User 拥有多个 Post
@Children(for: \.author)
var posts: [Post]
init() {} // Fluent 要求无参数初始化器
init(id: UUID? = nil, name: String, email: String, passwordHash: String) {
self.id = id
self.name = name
self.email = email
self.passwordHash = passwordHash
}
}
// 字段名枚举(避免字符串散落各处)
extension User {
enum FieldKeys {
static let name: FieldKey = "name"
static let email: FieldKey = "email"
static let passwordHash: FieldKey = "password_hash"
static let createdAt: FieldKey = "created_at"
}
}
// Sources/App/Models/Post.swift
import Fluent
import Vapor
final class Post: Model, Content {
static let schema = "posts"
@ID(format: .uuid)
var id: UUID?
@Field(key: "title")
var title: String
@Field(key: "body")
var body: String
@Field(key: "published")
var published: Bool
// 多对一:每个 Post 属于一个 User(外键)
@Parent(key: "author_id")
var author: User
@Timestamp(key: "created_at", on: .create)
var createdAt: Date?
init() {}
init(title: String, body: String, published: Bool = false, authorID: UUID) {
self.title = title
self.body = body
self.published = published
self.$author.id = authorID
}
}
Migration:创建与管理表结构
Migration 是数据库结构变更的版本控制。每次表结构改动(新增列、删除索引等)都写成一个新的 Migration 类,而不是修改已有的 Migration,这样可以追踪所有变更历史,也可以回滚到任意版本。
// Sources/App/Migrations/CreateUser.swift
import Fluent
struct CreateUser: AsyncMigration {
// 正向迁移:创建 users 表
func prepare(on database: Database) async throws {
try await database.schema("users")
.id() // UUID 主键
.field("name", .string, .required)
.field("email", .string, .required)
.field("password_hash", .string, .required)
.field("created_at", .datetime)
.unique(on: "email") // email 唯一索引
.create()
}
// 回滚:删除表
func revert(on database: Database) async throws {
try await database.schema("users").delete()
}
}
// Sources/App/Migrations/CreatePost.swift
struct CreatePost: AsyncMigration {
func prepare(on database: Database) async throws {
try await database.schema("posts")
.id()
.field("title", .string, .required)
.field("body", .string, .required)
.field("published", .bool, .required)
.field("author_id", .uuid, .required,
.references("users", "id", onDelete: .cascade))
.field("created_at",.datetime)
.create()
}
func revert(on database: Database) async throws {
try await database.schema("posts").delete()
}
}
// configure.swift — 注册 Migration
public func configure(_ app: Application) async throws {
// 配置数据库(PostgreSQL)
app.databases.use(.postgres(configuration: .init(
hostname: Environment.get("DB_HOST") ?? "localhost",
port: Int(Environment.get("DB_PORT") ?? "5432") ?? 5432,
username: Environment.get("DB_USER") ?? "vapor",
password: Environment.get("DB_PASS") ?? "vapor",
database: Environment.get("DB_NAME") ?? "vapor_db"
)), as: .psql)
// 按顺序注册 Migration(顺序很重要!外键依赖)
app.migrations.add(CreateUser())
app.migrations.add(CreatePost())
// 开发时自动运行 Migration(生产环境慎用)
try await app.autoMigrate()
}
# 手动执行迁移
swift run App migrate
# 回滚最后一批迁移
swift run App migrate --revert
QueryBuilder:查询组合
Fluent 的 QueryBuilder 采用链式调用风格,每个方法返回新的 QueryBuilder,最终调用终结方法(.all()、.first()等)触发实际 SQL 执行。
Swift QueryBuilder 链:
User.query(on: db)
.filter(\.$email == "alice@example.com") → WHERE email = 'alice@example.com'
.filter(\.$published == true) → AND published = true
.sort(\.$createdAt, .descending) → ORDER BY created_at DESC
.range(0..<10) → LIMIT 10 OFFSET 0
.with(\.$posts) → LEFT JOIN posts (预加载关联)
.all() → 执行 SQL,返回 [User]
生成 SQL(近似):
SELECT users.*, posts.*
FROM users
LEFT JOIN posts ON posts.author_id = users.id
WHERE users.email = 'alice@example.com'
AND posts.published = true
ORDER BY users.created_at DESC
LIMIT 10
// 常用查询示例
// 1. 按条件过滤
let activeUsers = try await User.query(on: db)
.filter(\.$email, .hasSuffix, "@example.com")
.all()
// 2. 预加载关联(避免 N+1 查询问题)
let users = try await User.query(on: db)
.with(\.$posts) { posts in
posts.filter(\.$published == true) // 只加载已发布的文章
}
.all()
// 3. 聚合查询
let count = try await Post.query(on: db)
.filter(\.$published == true)
.count()
// 4. 数据库事务(原子性)
try await db.transaction { txDB in
let user = User(name: "Alice", email: "alice@e.com", passwordHash: "hash")
try await user.save(on: txDB)
let post = Post(title: "Hello", body: "World", authorID: user.id!)
try await post.save(on: txDB)
// 任意 throw 都会自动回滚两条操作
}
生产环境慎用 autoMigrate
app.autoMigrate() 在每次启动时自动执行未运行的 Migration,方便开发,但在生产环境中可能造成意外。生产部署建议在 CI/CD 中显式执行 migrate 命令,并做好数据备份后再运行。
本章小结
Fluent 通过属性包装器(@ID、@Field、@Parent、@Children)建立类型安全的数据库映射;Migration 提供可回滚的数据库版本管理;QueryBuilder 的链式 API 组合出清晰的查询逻辑。下一章将学习如何处理 HTTP 请求体与响应,深入 Content 协议和错误处理。