- 支持试读
为什么选择 sqlx 而不是 ORM?
本章我们先讨论一个问题:为什么选择 sqlx 而不是 ORM? - 支持试读
基本CRUD
本章我们将讨论使用 sqlx 和 PostgreSQL 执行基本的 CRUD (增删改查)操作。 Executor
本章我们探讨 sqlx 使用最频繁的 trait:Executor。同时还将讨论如何通过参数传递事务。- 支持试读
使用 sqlx 操作 PostgreSQL 数组
PostgreSQL 原生支持数组。本章将讨论如何使用 sqlx 操作 PostgreSQL 的数组。 把 PostgreSQL 变成 MongoDB
MongoDB 等 NoSQL 异军突起的原因之一就是解决了传统关系型数据库的一大痛点:数据的扩展性,与此同时,NoSQL 又丧失了关系型数据库的范式。PostgreSQL 原生支持 JSON,通过这一特性,可以将 PostgreSQL 打造为同时兼备关系型数据库和 NoSQL 数据库的六边形数据库。把 PostgreSQL 变成 Redis
PostgreSQL 支持 hstore 数据类型:一种简单的键/值对。配合无日志表,我们可以将 PostgreSQL 打造为简单的缓存服务。把 PostgreSQL 变成消息队列
PostgreSQL 原生支持异步通知。本章我们将探讨通过 PostgreSQL 的异步通知,打造一个消息推送服务。
基本CRUD
- 16
- 2025-01-18 15:48:49
本章我们将讨论使用 sqlx 和 PostgreSQL 执行基本的 CRUD (增删改查)操作。
示例数据
我们以一个非常简单的“分类”表来演示基本的 CRUD 操作,SQL如下:
CREATE TABLE IF NOT EXISTS "categories" (
"id" SERIAL PRIMARY KEY,
"name" VARCHAR(255) NOT NULL
);
模型定义
- 为了让 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)
}
-
PostgreSQL 支持
RETURNING
子句。本例中的INSERT
语句会把新插入的id
值返回 -
为了获取到新插入的
id
,我们配合query_as
和fetch_one
,有关这两个方法的说明,请参见下一章 -
bind()
:用于绑定参数。注意,PostgreSQL 的参数占位符是$n
,其中n
是从1
开始的编号,表示第n
个参数 -
Result
是我们自定义的类型,其定义如下:// src/err.rs use axum::response::IntoResponse; #[derive(Debug)] pub struct Error(anyhow::Error); impl Error { pub fn new(msg: &str) -> Self { Self(anyhow::anyhow!("{}", msg)) } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self.0) } } impl<E> From<E> for Error where E: Into<anyhow::Error>, { fn from(e: E) -> Self { Self(e.into()) } } impl IntoResponse for Error { fn into_response(self) -> axum::response::Response { self.0.to_string().into_response() } } // src/lib.rs mod err; pub use err::Error; pub type Result<T> = std::result::Result<T, crate::Error>;
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)
}
对应的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/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))
}
查询多条记录
对应的handler:
// 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
分支。