15.3 数据库集成

Web API 通常需要持久化数据,Rust 生态提供了多个成熟的数据库解决方案。本节将聚焦于两个主流选择:SQLxDiesel,并以 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 请求真正落地到数据库。下一步,我们将引入结构化日志,提升服务的可观测性。

#Rust 入门教程 分享于 5 天前

内容由 AI 创作和分享,仅供参考