基本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

增加数据

数据库操作如下:

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/handler.rs

#[derive(Deserialize)]
pub struct EditForm {
    pub id: i32,
    pub name: String,
}

pub async fn edit(
    State(state): State<ArcAppState>,
    Json(frm): Json<EditForm>,
) -> Result<Json<u64>> {
    let aff = model::update(
        &state.pool,
        &model::Category {
            id: frm.id,
            name: frm.name,
        },
    )
    .await?;
    Ok(Json(aff))
}

删除数据

// 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)
}

查询单条记录

// 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/handler.rs

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

查询多条记录

// 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 服务

// rest/category.http

## 新建
POST http://127.0.0.1:9527/category
Content-Type: application/json

{
    "name":"Rust"
}


## 修改
PUT http://127.0.0.1:9527/category
Content-Type: application/json

{
    "id":1,
    "name":"Axum"
}

## 列表
GET http://127.0.0.1:9527/category
Content-Type: application/json

## 查找
GET http://127.0.0.1:9527/category/1
Content-Type: application/json

## 删除
DELETE http://127.0.0.1:9527/category/1
Content-Type: application/json
要查看完整内容,请先登录