Chuyển đến nội dung chính

第 11 課:SQLx 和資料庫集成

SQLx 非同步、編譯時查詢檢查。遷移、連接池。 CRUD 操作、交易。 Sea-ORM 替代方案。儲存庫模式,乾淨的架構。

💻 程式設計 — 第 11 課 第 11 課:SQLx 和資料庫集成

Rust:從基礎到高級

第 3 部分:非同步 Rust 和 Web 開發

亞洲開發網

1.SQLx設定

[dependencies]
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "uuid", "chrono"] }
# Cài SQLx CLI
cargo install sqlx-cli --no-default-features --features postgres

# Tạo database
sqlx database create

# Tạo migration
sqlx migrate add create_products_table
-- migrations/001_create_products_table.sql
CREATE TABLE products (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(255) NOT NULL,
    price NUMERIC(10,2) NOT NULL,
    description TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

2.增刪改查操作

use sqlx::{PgPool, FromRow};
use uuid::Uuid;
use chrono::{DateTime, Utc};

#[derive(Debug, FromRow, Serialize)]
struct Product {
    id: Uuid,
    name: String,
    price: rust_decimal::Decimal,
    description: Option<String>,
    created_at: DateTime<Utc>,
}

struct ProductRepository {
    pool: PgPool,
}

impl ProductRepository {
    // Compile-time verified query
    async fn find_all(&self, limit: i64, offset: i64) -> Result<Vec<Product>, sqlx::Error> {
        sqlx::query_as!(
            Product,
            "SELECT id, name, price, description, created_at FROM products ORDER BY created_at DESC LIMIT $1 OFFSET $2",
            limit, offset
        )
        .fetch_all(&self.pool)
        .await
    }

    async fn find_by_id(&self, id: Uuid) -> Result<Option<Product>, sqlx::Error> {
        sqlx::query_as!(Product, "SELECT * FROM products WHERE id = $1", id)
            .fetch_optional(&self.pool)
            .await
    }

    async fn create(&self, name: &str, price: Decimal, description: Option<&str>) -> Result<Product, sqlx::Error> {
        sqlx::query_as!(
            Product,
            "INSERT INTO products (name, price, description) VALUES ($1, $2, $3) RETURNING *",
            name, price, description
        )
        .fetch_one(&self.pool)
        .await
    }

    async fn delete(&self, id: Uuid) -> Result<bool, sqlx::Error> {
        let result = sqlx::query!("DELETE FROM products WHERE id = $1", id)
            .execute(&self.pool)
            .await?;
        Ok(result.rows_affected() > 0)
    }
}

3. 交易

async fn transfer_stock(
    pool: &PgPool,
    from_id: Uuid,
    to_id: Uuid,
    quantity: i32,
) -> Result<(), sqlx::Error> {
    let mut tx = pool.begin().await?;

    sqlx::query!("UPDATE inventory SET stock = stock - $1 WHERE product_id = $2", quantity, from_id)
        .execute(&mut *tx).await?;

    sqlx::query!("UPDATE inventory SET stock = stock + $1 WHERE product_id = $2", quantity, to_id)
        .execute(&mut *tx).await?;

    tx.commit().await?;
    Ok(())
}

4. 連接池

use sqlx::postgres::PgPoolOptions;

let pool = PgPoolOptions::new()
    .max_connections(20)
    .min_connections(5)
    .acquire_timeout(Duration::from_secs(5))
    .idle_timeout(Duration::from_secs(600))
    .connect(&database_url)
    .await?;

// Run migrations
sqlx::migrate!("./migrations").run(&pool).await?;

下一篇: 認證與授權。