0Pricing
Learn Rust Coding · 课时

数据库集成

持久化数据

数据库集成 是 CoddyKit 上的免费 Learn Rust Coding 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Learn Rust Coding 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Learn Rust Coding 课程共包含 4 节课。

持久化数据

到目前为止,我们一直将数据存储在内存中,重启后数据就会消失。实际服务需要数据库。本课将使用 sqlx 将 Axum API 连接到 PostgreSQL;sqlx 是一个适用于 Rust 的异步、编译时检查 SQL 工具包。

您将学习连接池、查询、将行映射到结构体,以及将连接池用作共享状态。

添加 sqlx

根据需要添加 sqlx 的功能:运行时、TLS 和数据库驱动。下面是使用 Tokio 的 Postgres 配置。

// Cargo.toml
// [dependencies]
// sqlx = { version = "0.7", features = [
//   "runtime-tokio", "tls-rustls", "postgres", "macros"
// ] }

连接池

每次请求都新建连接会很慢。连接池会维护一组可重复使用的连接。PgPoolOptions 根据数据库 URL 构建连接池。连接池的克隆成本很低(其内部使用了引用计数),因此非常适合作为共享状态。

use sqlx::postgres::PgPoolOptions;

async fn make_pool(url: &str) -> sqlx::PgPool {
    PgPoolOptions::new()
        .max_connections(5)
        .connect(url)
        .await
        .expect("failed to connect")
}

将连接池用作应用状态

使用 .with_state(pool) 将连接池传递给 Axum。处理函数随后接收 State(pool): State<PgPool>。由于克隆连接池的成本很低,每个请求都可以共享同一组底层连接。

use axum::{routing::get, Router};
use sqlx::PgPool;

fn build(pool: PgPool) -> Router {
    Router::new()
        .route("/todos", get(list_todos))
        .with_state(pool)
}

执行查询

sqlx::query 函数用于执行原始 SQL。使用 .bind(value) 绑定参数,以避免 SQL 注入;Postgres 使用 $1、$2 作为占位符。对于不返回行的写入操作,请使用 .execute(&pool)。

use sqlx::PgPool;

async fn insert_todo(pool: &PgPool, title: &str) -> Result<(), sqlx::Error> {
    sqlx::query("INSERT INTO todos (title, done) VALUES ($1, $2)")
        .bind(title)
        .bind(false)
        .execute(pool)
        .await?;
    Ok(())
}

将行映射到结构体

为模型派生 sqlx::FromRow,这样查询结果就能直接映射到模型。使用 query_as::<_, Todo> 配合 fetch_all 获取 Vec<Todo>,或使用 fetch_one 获取单行。

use sqlx::{PgPool, FromRow};

#[derive(FromRow, serde::Serialize)]
struct Todo { id: i32, title: String, done: bool }

async fn all_todos(pool: &PgPool) -> Result<Vec<Todo>, sqlx::Error> {
    let rows = sqlx::query_as::<_, Todo>("SELECT id, title, done FROM todos")
        .fetch_all(pool)
        .await?;
    Ok(rows)
}

读取数据库的处理函数

将各部分组合起来:处理函数从状态中获取连接池,执行查询,然后返回 JSON。将数据库错误映射为 500 状态,以便客户端获得整洁的响应,而不是遇到程序崩溃。

use axum::{extract::State, Json, http::StatusCode};
use sqlx::PgPool;

async fn list_todos(
    State(pool): State<PgPool>,
) -> Result<Json<Vec<Todo>>, StatusCode> {
    match all_todos(&pool).await {
        Ok(todos) => Ok(Json(todos)),
        Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
    }
}

返回插入的行

Postgres 可以使用 RETURNING 返回刚刚插入的行。将它与 query_as 和 fetch_one 结合使用,即可在一次往返中获取包括自动生成 id 在内的新记录。

use sqlx::PgPool;

async fn create(pool: &PgPool, title: &str) -> Result<Todo, sqlx::Error> {
    let todo = sqlx::query_as::<_, Todo>(
        "INSERT INTO todos (title, done) VALUES ($1, false) \
         RETURNING id, title, done")
        .bind(title)
        .fetch_one(pool)
        .await?;
    Ok(todo)
}

迁移

执行查询前,数据库模式必须已经存在。sqlx 支持迁移:存放在 migrations/ 文件夹中的 SQL 文件会按顺序应用。在启动时运行 sqlx::migrate!(),即可自动设置全新的数据库。

use sqlx::PgPool;

async fn run_migrations(pool: &PgPool) {
    sqlx::migrate!("./migrations")
        .run(pool)
        .await
        .expect("migrations failed");
}
// migrations/0001_init.sql contains the CREATE TABLE statements.

事务

当多个写入操作必须一起成功时,请将它们封装在事务中。使用 pool.begin() 开始事务,针对事务句柄执行查询,然后调用 commit。如果事务未提交就被丢弃,sqlx 会自动回滚,从而保持数据一致。

use sqlx::PgPool;

async fn transfer(pool: &PgPool) -> Result<(), sqlx::Error> {
    let mut tx = pool.begin().await?;
    sqlx::query("UPDATE accounts SET balance = balance - 10 WHERE id = 1")
        .execute(&mut *tx).await?;
    sqlx::query("UPDATE accounts SET balance = balance + 10 WHERE id = 2")
        .execute(&mut *tx).await?;
    tx.commit().await?;
    Ok(())
}

配置和密钥

永远不要将数据库凭据硬编码。请从环境中读取 DATABASE_URL;在开发期间,通常会使用 dotenvy crate 从 .env 文件加载该变量。在生产环境中,平台会将其注入为环境变量。

use std::env;

async fn connect_from_env() -> sqlx::PgPool {
    let url = env::var("DATABASE_URL")
        .expect("DATABASE_URL must be set");
    make_pool(&url).await
}

快速检查

测试您对数据库集成的理解。

回顾

您已经集成了数据库:

  • 使用 PgPool 连接池,并通过 .with_state 共享它。
  • 写入操作使用 query/execute;类型化读取使用带有 FromRow 的 query_as。
  • 始终绑定参数,以防止 SQL 注入。
  • RETURNING 可获取插入的行;事务会以原子方式将多个写入操作组合在一起。
  • 在启动时运行迁移,并从环境中读取 DATABASE_URL。

常见问题解答

「数据库集成」课时是免费的吗?

是的 — 「数据库集成」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Learn Rust Coding 课程的其余内容,请升级到 CoddyKit PRO。 Learn Rust Coding 课程共包含 4 节课。

「数据库集成」这节课中我会学到什么?

持久化数据 你通过在浏览器中直接运行的动手代码来练习 Learn Rust Coding,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Learn Rust Coding 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Learn Rust Coding 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「数据库集成」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Learn Rust Coding 课中编写并运行代码吗?

能。每节 Learn Rust Coding 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 项目设置
  2. 端点与模型
  3. 数据库集成
  4. 测试 API
← 返回 Learn Rust Coding