15.3 数据库集成
Web API 通常需要持久化数据,Rust 生态提供了多个成熟的数据库解决方案。本节将聚焦于两个主流选择:SQLx 和 Diesel,并以 SQLx 为主进行实战演示,因其对异步原生支持、编译期查询检查以及零宏 DSL 的设计,更契合现代 Rust Web 项目的趋势。
SQLx 简介
SQLx 是一个“无运行时宏”的异步 SQL 库,支持 PostgreSQL、MySQL、SQLite 和 MSSQL。其核心特性包括:
- 完全异步,基于
tokio; - 编译期 SQL 检查:通过
sqlx::query!宏在编译时验证 SQL 语法和列类型; - 无需 ORM,直接写 SQL,避免抽象泄漏;
- 支持连接池、迁移工具(
sqlx-cli)。
添加依赖与初始化
在 Cargo.toml 中添加:
[dependencies]
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "uuid", "chrono"] }
tokio = { version = "1.0", features = ["full"] }
根据所用数据库调整 feature,例如 SQLite 使用
["runtime-tokio-native-tls", "sqlite"]。
安装 sqlx-cli 用于管理迁移:
cargo install sqlx-cli --no-default-features --features native-tls,postgres
定义模型与表结构
假设任务表如下:
CREATE TABLE tasks (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
completed BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
对应的 Rust 模型(src/models/task.rs):
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use chrono::{DateTime, Utc};
#[derive(Serialize, Deserialize, FromRow, Debug)]
pub struct Task {
pub id: i32,
pub title: String,
pub completed: bool,
#[serde(rename = "created_at")]
pub created_at: DateTime<Utc>,
}
#[derive(Deserialize)]
pub struct CreateTask {
pub title: String,
}
#[derive(FromRow)] 允许 SQLx 自动将查询结果映射到结构体。
实现 Repository
在 src/repositories/task_repo.rs 中封装数据库操作:
use sqlx::{PgPool, Error as SqlxError};
use crate::models::task::{CreateTask, Task};
pub struct TaskRepository {
pool: PgPool,
}
impl TaskRepository {
pub fn new(pool: PgPool) -> Self {
Self { pool }
}
pub async fn create(&self, task: CreateTask) -> Result<Task, SqlxError> {
let row = sqlx::query_as!(
Task,
"INSERT INTO tasks (title) VALUES ($1) RETURNING *",
task.title
)
.fetch_one(&self.pool)
.await?;
Ok(row)
}
pub async fn find_by_id(&self, id: i32) -> Result<Task, SqlxError> {
let task = sqlx::query_as!(Task, "SELECT * FROM tasks WHERE id = $1", id)
.fetch_one(&self.pool)
.await?;
Ok(task)
}
}
注意使用 query_as! 宏:它在编译时检查 SQL 是否返回与 Task 匹配的列,若表结构变更而未更新模型,编译将失败——这是 SQLx 的核心安全机制。
数据库连接与迁移
在 src/main.rs 中初始化连接池:
use sqlx::PgPool;
async fn init_db() -> PgPool {
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
PgPool::connect(&database_url).await.unwrap()
}
创建迁移:
sqlx migrate add create_tasks
# 编辑 migrations/xxxx_create_tasks.sql
sqlx migrate run
与 Diesel 的对比
Diesel 是 Rust 中历史悠久的 ORM,特点包括:
- 强大的查询构建器(DSL);
- 编译期类型安全(通过
diesel_codegen); - 对同步代码友好,异步支持较新(需
diesel-async); - 学习曲线较陡,抽象层级更高。
SQLx 更适合希望直接控制 SQL、偏好显式而非隐式行为的开发者;Diesel 则适合复杂查询建模或已有同步代码库的项目。
小结
通过 SQLx,我们实现了类型安全、异步高效的数据库访问,并将其封装在 repository 层,与业务逻辑解耦。结合前一节的 Axum handler,现在可以将 HTTP 请求真正落地到数据库。下一步,我们将引入结构化日志,提升服务的可观测性。