域名 AXUM.RS 将于 2025 年 10 月到期。我们无意再对其进行续费,如果你有意接续这个域名,请与我们取得联系。
  • AXUM.RS 现仅需人民币 3000 元(大写:叁仟元整。接受适度议价
  • 按照行业规则,AXUM.RS 到期后,大概率会进入长时间的赎回期,该期间内,如果你想拥有该域名,将要付出高额的费用
  • 我们已启用 AXUM.EU.ORG 域名,并将持续运营
  • 仅接受微信或支付宝交易
如果你对 AXUM.RS 有兴趣,请和我们进行联系:

基本CRUD

本章我们将讨论使用 sqlx 和 PostgreSQL 执行基本的 CRUD (增删改查)操作。

示例数据

我们以一个非常简单的“分类”表来演示基本的 CRUD 操作,SQL如下:

CREATE TABLE IF NOT EXISTS "categories" (
    "id" SERIAL PRIMARY KEY,
    "name" VARCHAR(255) NOT NULL
);

模型定义

// src/category/model.rs

#[derive(Debug, Default, Deserialize, Serialize, FromRow)]
pub struct Category {
    pub id: i32,
    pub name: String,
}
  • 为了让 sqlx 自动将数据库记录映射为 rust 结构体,我们需要使用 sqlx::FromRow

增加数据

数据库操作如下:

// src/category/model.rs

pub async fn insert(p: &PgPool, m: &Category) -> Result<i32> {
    let (id,): (i32,) =
        sqlx::query_as(r#"INSERT INTO categories ("name") VALUES ($1) RETURNING id"#)
            .bind(&m.name)
            .fetch_one(p)
            .await?;
    Ok(id)
}

handler如下:

// src/category/handler.rs

#[derive(Deserialize)]
pub struct CreateForm {
    pub name: String,
}

pub async fn create(
    State(state): State<ArcAppState>,
    Json(frm): Json<CreateForm>,
) -> Result<Json<i32>> {
    let id = model::insert(
        &state.pool,
        &model::Category {
            name: frm.name,
            ..Default::default()
        },
    )
    .await?;
    Ok(Json(id))
}
  • 我们使用了 State():用于在 axum 应用中共享数据。其中的 ArcAppState 是我们定义的共享状态:

    // src/lib.rs
    pub struct AppState {
        pub pool: sqlx::PgPool,
    }
    
    pub type ArcAppState = std::sync::Arc<AppState>;
    

修改数据

// src/category/model.rs

pub async fn update(p: &PgPool, m: &Category) -> Result<u64> {
    let aff = sqlx::query(r#"UPDATE categories SET "name" = $1 WHERE id = $2"#)
        .bind(&m.name)
        .bind(&m.id)
        .execute(p)
        .await?
        .rows_affected();
    Ok(aff)
}

handler如下:

删除数据

// src/category/model.rs

pub async fn delete(p: &PgPool, id: i32) -> Result<u64> {
    let aff = sqlx::query("DELETE FROM categories WHERE id = $1")
        .bind(id)
        .execute(p)
        .await?
        .rows_affected();
    Ok(aff)
}

对应的handler:

// src/category/handler.rs

pub async fn delete(State(state): State<ArcAppState>, Path(id): Path<i32>) -> Result<Json<u64>> {
    let aff = model::delete(&state.pool, id).await?;
    Ok(Json(aff))
}

查询单条记录

// src/category/model.rs

pub async fn find(p: &PgPool, id: i32) -> Result<Option<Category>> {
    let m = sqlx::query_as(r#"SELECT id, "name" FROM categories WHERE id = $1"#)
        .bind(id)
        .fetch_optional(p)
        .await?;
    Ok(m)
}

对应的handler:

查询多条记录

// src/category/model.rs

pub async fn list(p: &PgPool) -> Result<Vec<Category>> {
    let m = sqlx::query_as(r#"SELECT id, "name" FROM categories ORDER BY id DESC LIMIT 100"#)
        .fetch_all(p)
        .await?;
    Ok(m)
}

对应的handler:

// src/category/handler.rs

pub async fn list(State(state): State<ArcAppState>) -> Result<Json<Vec<model::Category>>> {
    let ls = model::list(&state.pool).await.unwrap();
    Ok(Json(ls))
}

路由定义

// src/main.rs

fn router_init(state: ArcAppState) -> Router {
    let category_router = Router::new()
        .route(
            "/",
            get(category::handler::list)
                .post(category::handler::create)
                .put(category::handler::edit),
        )
        .route(
            "/{id}",
            get(category::handler::find).delete(category::handler::delete),
        );

    Router::new()
        .nest("/category", category_router)
        .with_state(state)
}
  • axum v0.8 有一个重大改动:动态路由的参数由原来的 /:参数名 改为了 /{参数名},如本例的 route("/{id}", ...)

测试

我们使用 VSCODE 的 REST Client 插件来测试 HTTP 服务

本章代码位于01.crud分支。

要查看完整内容,请先登录