数据库集成
持久化数据
数据库集成 是 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 反馈 — 无需本地设置。