第四章:数据库 CRUD 操作与分页查询
博客v1.0系列教程(Rust)博客 v1.0 系列教程 (Rust)
4.1 文章服务层
// src/services/article_service.rs
use sqlx::PgPool;
use crate::models::entity::Article;
use crate::models::dto::{ArticleDto, CreateArticleDto, PageResult};
pub struct ArticleService {
pool: PgPool,
}
impl ArticleService {
pub fn new(pool: PgPool) -> Self {
Self { pool }
}
/// 分页查询文章
pub async fn list_articles(
&self,
page: u64,
page_size: u64,
category: Option<&str>,
keyword: Option<&str>,
) -> Result<PageResult<Article>, sqlx::Error> {
let offset = (page - 1) * page_size;
// 构建动态查询
let mut count_sql = String::from("SELECT COUNT(*) FROM article_models WHERE 1=1");
let mut query_sql = String::from(
"SELECT * FROM article_models WHERE 1=1"
);
let mut params: Vec<String> = Vec::new();
if let Some(cat) = category {
let idx = params.len() + 1;
count_sql.push_str(&format!(" AND category = ${}", idx));
query_sql.push_str(&format!(" AND category = ${}", idx));
params.push(cat.to_string());
}
if let Some(kw) = keyword {
let idx = params.len() + 1;
count_sql.push_str(&format!(" AND title ILIKE ${}", idx));
query_sql.push_str(&format!(" AND title ILIKE ${}", idx));
params.push(format!("%{}%", kw));
}
query_sql.push_str(" ORDER BY create_time DESC");
query_sql.push_str(&format!(" LIMIT ${} OFFSET ${}", params.len() + 1, params.len() + 2));
// 构建查询
let mut count_query = sqlx::query_scalar::<_, i64>(&count_sql);
let mut data_query = sqlx::query_as::<_, Article>(&query_sql);
for param in ¶ms {
count_query = count_query.bind(param);
data_query = data_query.bind(param);
}
let total: i64 = count_query
.bind(¶ms)
.fetch_one(&self.pool)
.await?;
let items = data_query
.bind(page_size as i64)
.bind(offset as i64)
.fetch_all(&self.pool)
.await?;
Ok(PageResult {
items,
total: total as u64,
page,
page_size,
})
}
}
4.2 数据传输对象
// src/models/dto.rs
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
pub struct CreateArticleDto {
pub title: String,
pub content: String,
pub category: Option<String>,
pub tags: Option<Vec<String>>,
}
#[derive(Debug, Deserialize)]
pub struct ArticleQuery {
pub page: Option<u64>,
pub page_size: Option<u64>,
pub category: Option<String>,
pub keyword: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct PageResult<T: Serialize> {
pub items: Vec<T>,
pub total: u64,
pub page: u64,
pub page_size: u64,
}
#[derive(Debug, Serialize)]
pub struct ApiResult<T: Serialize> {
pub code: i32,
pub message: String,
pub data: Option<T>,
}
impl<T: Serialize> ApiResult<T> {
pub fn success(data: T) -> Self {
ApiResult {
code: 0,
message: "success".into(),
data: Some(data),
}
}
pub fn error(code: i32, message: &str) -> Self {
ApiResult {
code,
message: message.into(),
data: None,
}
}
}
4.3 事务操作
pub async fn create_article_with_tags(
&self,
dto: CreateArticleDto,
) -> Result<Article, sqlx::Error> {
let mut tx = self.pool.begin().await?;
let article = sqlx::query_as::<_, Article>(
"INSERT INTO article_models (title, content, category, tags)
VALUES ($1, $2, $3, $4)
RETURNING *"
)
.bind(&dto.title)
.bind(&dto.content)
.bind(&dto.category)
.bind(&dto.tags.map(|t| t.join(",")))
.fetch_one(&mut *tx)
.await?;
tx.commit().await?;
Ok(article)
}
4.4 批量操作
pub async fn batch_update_category(
&self,
old_category: &str,
new_category: &str,
) -> Result<u64, sqlx::Error> {
let result = sqlx::query(
"UPDATE article_models SET category = $1 WHERE category = $2"
)
.bind(new_category)
.bind(old_category)
.execute(&self.pool)
.await?;
Ok(result.rows_affected())
}
下一章将实现 JWT 认证中间件。
rustsqlxcrudpaginationquery