编程 Toasty ORM深度实战:Tokio团队打造的生产级异步数据库框架

2026-05-21 17:52:28 +0800 CST views 10

Toasty ORM 深度实战:Tokio 团队打造的生产级异步数据库框架

2026年是Rust生态真正走向成熟的一年。从Oxc重写ESLint,到Rolldown颠覆打包器,再到Pyrefly用Rust重写Python类型检查——Rust正在吃掉整个前端工具链。而在后端领域,Rust生态也在经历一场静默的革命。2026年4月,Tokio团队正式发布了Toasty——一个全新的异步ORM框架,它不仅仅是一个SQL生成工具,而是定位为应用级查询引擎。本文将深入解析Toasty的核心设计理念、架构原理,并通过完整的生产级代码示例,带你全面掌握这个可能改变Rust后端开发格局的框架。

一、为什么还需要另一个Rust ORM?

在Toasty出现之前,Rust生态已经有不少成熟的ORM方案:

  • SQLx:最流行的异步SQL构建器,纯SQL编写,强类型检查
  • SeaORM:SeaQL团队出品,依赖SeaPipe DSL
  • Diesel:同步ORM的事实标准,类型安全DSL
  • Rorm/Rbatis:国产高性能ORM

既然有这么多选择,Tokio团队为什么还要再造一个ORM?这要从一个问题说起。

1.1 当代ORM的困境

让我们先看一个典型的用户查询场景:

// 假设我们要查询用户及其最近10条订单
// SQLx写法:手写SQL
let rows = sqlx::query_as!(
    UserWithOrders,
    r#"SELECT u.id, u.name, u.email, o.id as order_id, o.total 
       FROM users u 
       LEFT JOIN orders o ON o.user_id = u.id 
       WHERE u.id = ? 
       ORDER BY o.created_at DESC 
       LIMIT 10"#,
    user_id
).fetch_all(&pool).await?;

问题在哪?

  1. SQL与代码分离:维护困难,改动时容易出错
  2. 类型安全丢失:手动映射,容易对不上
  3. 数据库特性被忽视:每个数据库都有自己的特性,但ORM倾向于抹平

再看SeaORM的查询:

// SeaORM写法
let user_with_orders = User::find()
    .find_with_relation(Relations::Orders)
    .filter(User::Column::Id.eq(user_id))
    .order_by(Orders::Column::CreatedAt, Order::Desc)
    .limit(10)
    .await?;

问题:

  1. 学习成本高:需要学习一套新的DSL
  2. 灵活性与复杂性trade-off:高级特性使用门槛高

1.2 Toasty的破局思路

Tokio团队的答案是:不做SQL生成工具,而是做应用级查询引擎

Toasty的核心洞察是:

  • 应用层面的schema应该与数据库schema解耦
  • 数据库特性不应该被抹平,而应该被充分利用
  • 开发者应该用Rust的原生方式来表达数据关系,而不是学一套新DSL
#[derive(Debug, toasty::Model)]
struct User {
    #[key]
    #[auto]
    id: u64,
    name: String,
    #[unique]
    email: String,
    #[has_many]
    orders: toasty::HasMany<Order>,
}

#[derive(Debug, toasty::Model)]
struct Order {
    #[key]
    #[auto]
    id: u64,
    #[index]
    user_id: u64,
    total: Decimal,
    #[belongs_to(key = user_id, references = id)]
    user: toasty::BelongsTo<User>,
}

// 直接用Rust的方式查询
let orders = Order::find()
    .filter(Order::user_id.eq(user_id))
    .order_by.desc(Order::total)
    .limit(10)
    .all(&mut db)
    .await?;

这就是Toasty的哲学:让Rust做Rust擅长的事,用所有权和类型系统来保证安全

二、Toasty核心架构解析

2.1 宏观架构

Toasty的整体架构分为四层:

┌─────────────────────────────────────────────┐
│         Application Layer                  │
│   (User define Models, Queries, Mutations)     │
├───────────��─────────────────────────────────┤
│         Query Engine Layer                   │
│   (Operator overloading, Type inference)   │
├─────────────────────────────────────────────┤
│         Driver Abstraction Layer            │
│   (Driver trait: SQLite, PG, MySQL, DynamoDB) │
├─────────────────────────────────────────────┤
│         Database Driver Layer             │
│   (sqlx, aws-sdk-dynamodb, etc.)         │
└─────────────────────────────────────────────┘

核心文件结构(从GitHub源码):

toasty/
├── crates/
│   ├── core/           # 核心查询引擎
│   ├── driver-sqlite/    # SQLite驱动
│   ├── driver-postgres/# PostgreSQL驱动  
│   ├── driver-mysql/   # MySQL驱动
│   └── driver-aws/    # DynamoDB驱动(NoSQL)
├── macros/            # #[derive(toasty::Model)] 等宏
└── examples/         # 示例代码

2.2 Model定义系统

Toasty的模型定义是其最创新的部分。它使用Rust的proc_macro来自動生成数据库操作代码:

use toasty::schema::*;
use toasty::driver::sqlite::Sqlite;

// 定义用户模型
#[derive(Debug, Schema)]
#[toasty(schema = "users", driver = Sqlite)]
pub struct User {
    // 主键,auto表示自增
    #[key]
    #[auto]
    pub id: i64,
    
    pub name: String,
    
    // 唯一索引
    #[unique]
    pub email: String,
    
    pub created_at: DateTime<Utc>,
    
    // 一对多关系:用户有很多订单
    #[has_many(Order)]
    pub orders: HasMany<Order>,
}

// 定义订单模型
#[derive(Debug, Schema)]
#[toasty(schema = "orders", driver = Sqlite)]
pub struct Order {
    #[key]
    #[auto]
    pub id: i64,
    
    // 外键关系
    #[index]
    pub user_id: i64,
    
    pub total:.Decimal,
    
    pub status: OrderStatus,
    
    #[belongs_to(User)]
    pub user: BelongsTo<User>,
}

// 枚举类型
#[derive(Debug, EnumDisplay, EnumFromDb)]
pub enum OrderStatus {
    Pending,
    Paid,
    Shipped,
    Delivered,
    Cancelled,
}

编译时自动生成:

  • User::get_by_id() - 按ID查询
  • User::get_by_email() - 按邮箱查询(唯一索引加速)
  • User::create() - 插入
  • User::update() - 更新
  • User::delete() - 删除
  • User::orders() - 获取关联的订单

2.3 查询表达式系统

Toasty的查询采用了operator重载,让你能用自然的Rust表达式来构建查询:

use toasty::ops::*;

// 基础查询
let user = User::get_by_id(&mut db, 1).await?;

// 条件查询 - 完全Rust风格
let users = User::find()
    .filter(User::email.like("%@example.com"))
    .filter(User::created_at.gt(datetime::Utc::now() - Duration::days(30)))
    .order_by(User::name)
    .limit(100)
    .all(&mut db)
    .await?;

// 复杂的AND/OR组合
let orders = Order::find()
    .filter(
        Order::user_id.eq(user_id).and(
            Order::status.eq(OrderStatus::Paid)
                .or(Order::status.eq(OrderStatus::Shipped))
        )
    )
    .filter(Order::total.gt(Decimal::from(100)))
    .order_by_desc(Order::created_at)
    .all(&mut db)
    .await?;

生成的SQL(PostgreSQL):

SELECT * FROM users 
WHERE email LIKE '%@example.com' 
  AND created_at > '2026-04-21'::timestamp
ORDER BY name 
LIMIT 100;

SELECT * FROM orders 
WHERE user_id = $1 
  AND (status = 'Paid' OR status = 'Shipped')
  AND total > 100
ORDER BY created_at DESC;

2.4 关系查询

这是Toasty最强的部分——它让你用Rust的类型系统来表达关系:

// 预加载模式:一次查询加载所有关联数据
let user = User::get_by_id(&mut db, 1)
    .expand(User::orders)  // 自动加载orders
    .await?;

// 结果已经包含关联数据
for order in user.orders {
    println!("Order {}: ${}", order.id, order.total);
}

// 查询订单时反向关联用户信息
let order = Order::get_by_id(&mut db, 100)
    .expand(Order::user)
    .await?;

println!("Order belongs to: {}", order.user.name);

// 过滤后预加载
let users = User::find()
    .filter(User::name.ilike("%john%"))
    .expand_with(User::orders, |q| {
        q.filter(Order::status.eq(OrderStatus::Delivered))
            .order_by_desc(Order::created_at)
            .limit(5)
    })
    .all(&mut db)
    .await?;

生成的JOIN SQL:

SELECT u.*, o.*
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.id = 1;

SELECT o.*, u.*
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.id = 100;

三、多数据库驱动详解

Toasty支持四种数据库,每种都有独特的优化。

3.1 SQLite驱动

最适合开发和测试,性能出色:

use toasty::driver::sqlite::{Sqlite, SqlitePool};

// 配置SQLite
let db = Sqlite::open("dev.db").await?;
let pool = SqlitePool::new(&db, 10).await?;

// 使用连接池
async {
    let mut conn = pool.acquire().await?;
    let users = User::find().limit(10).all(&mut conn).await?;
    Ok::<(), toasty::Error>(())
}.await?;

SQLite驱动特性:

  • WAL模式自动开启(写入性能提升3-5倍)
  • 全文搜索支持(FTS5)
  • 内存数据库支持(:memory:file::memory:?cache=shared
// 内存数据库,用于测试
let db = Sqlite::open(":memory:").await?;
// 或共享缓存模式
let db = Sqlite::open("file::memory:?cache=shared").await?;

3.2 PostgreSQL驱动

生产环境首选,完整支持PostgreSQL特性:

use toasty::driver::postgres::{Pg, PgPool};

// PostgreSQL连接
let conn_string = "postgres://user:password@localhost/mydb";
let pool = PgPool::connect(conn_string, 10).await?;

// JSON/JSONB高效支持
#[derive(Debug, toasty::Model)]
struct Config {
    #[key]
    pub id: i64,
    pub settings: Json<serde_json::Value>,  // JSONB类型
    pub tags: Vec<String>,              // 数组类型
}

// 查询JSON字段
let config = Config::find()
    .filter(Config::settings["theme"].eq("dark"))
    .filter(Config::tags.contains("pro"))
    .one(&mut conn)
    .await?;

PostgreSQL特有特性:

  • JSONB:完整索引支持,查询性能接近列存
  • ** ARRAY**:PostgreSQL数组类型原生支持
  • Range Types:范围类型(Int4Range, TsRange等)
  • Full Text Search:全文搜索
  • On_conflict:冲突解决(UPSERT)
// UPSERT操作
User::upsert()
    .set(User {
        name: "John Updated".into(),
        ..user  // 保持其他字段不变
    })
    .on_conflict(User::email)
    .do_nothing()
    .exec(&mut conn)
    .await?;

3.3 MySQL/MariaDB驱动

use toasty::driver::mysql::{MySql, MySqlPool};

let pool = MySqlPool::connect("mysql://user:password@localhost/mydb").await?;

MySQL特有考虑:

  • JSON类型(不是JSONB)
  • 不支持数组类型,需要JSON编码
  • charset utf8mb4 需要显式指定
#[derive(Debug, toasty::Model)]
struct Record {
    #[key]
    #[auto]
    pub id: i64,
    #[column(charset = "utf8mb4")]
    pub content: String,
    pub metadata: Json<serde_json::Value>,  // 序列化为JSON字符串
}

3.4 DynamoDB驱动(NoSQL)

这是Toasty最独特的地方��—��持NoSQL:

use toasty::driver::aws::{DynamoDb, DynamoDbClient};

let client = DynamoDbClient::new(region);
let db = DynamoDb::new(client);

// DynamoDB特有的分区键/排序键
#[derive(Debug, toasty::Model)]
#[toasty(schema = "orders", driver = DynamoDb)]
struct Order {
    #[key]                    // 分区键
    pub customer_id: String,
    #[sort_key]               // 排序键
    pub order_id: String,
    pub total: Decimal,
    pub items: Vec<OrderItem>,
}

// DynamoDB风格的查询
let orders = Order::find()
    .key_condition(Order::customer_id.eq(customer_id))
    .filter(Order::total.gt(100))
    .scan_index_forward(false)  // 倒序扫描
    .limit(20)
    .all(&mut db)
    .await?;

四、生产级实战:构建电商订单系统

现在我们来构建一个完整的电商订单系统,涵盖CRUD、事务、关系查询、性能优化。

4.1 项目初始化

[package]
name = "ecommerce-toasty"
version = "0.1.0"
edition = "2021"

[dependencies]
toasty = { version = "0.3", features = ["full"] }
toasty-macros = "0.3"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
decimal = "2"
chrono = { version = "0.4", features = ["serde"] }
thiserror = "1"

[dependencies.sqlx]
version = "0.8"
features = ["runtime-tokio", "sqlite", "postgres", "mysql"]

[dev-dependencies]
toasty = { version = "0.3", features = ["test"] }
tokio-test = "0.4"

4.2 定义完整的电商模型

// src/models/mod.rs

use toasty::schema::*;
use toasty::driver::postgres::Pg;
use decimal::Decimal;
use chrono::{DateTime, Utc};

// ============ 用户系统 ============

#[derive(Debug, Schema)]
#[toasty(schema = "users", driver = Pg)]
pub struct User {
    #[key]
    #[auto]
    pub id: i64,
    
    #[unique]
    pub username: String,
    
    #[unique]
    pub email: String,
    
    pub password_hash: String,
    
    pub is_active: bool,
    
    pub created_at: DateTime<Utc>,
    
    pub last_login_at: Option<DateTime<Utc>>,
    
    #[has_many(Order)]
    pub orders: HasMany<Order>,
    
    #[has_many(Review)]
    pub reviews: HasMany<Review>,
}

impl User {
    // 自定义查询方法
    pub fn find_by_username(db: &mut Db, username: &str) -> impl Future<Output = Result<Option<Self>, toasty::Error> {
        Self::find().filter(Self::username.eq(username)).one(db)
    }
    
    pub fn find_active_users(db: &mut Db, limit: i64) -> impl Future<Output = Result<Vec<Self>, toasty::Error> {
        Self::find()
            .filter(Self::is_active.eq(true))
            .order_by_desc(Self::created_at)
            .limit(limit)
            .all(db)
    }
}

// ============ 商品系统 ============

#[derive(Debug, Schema)]
#[toasty(schema = "products", driver = Pg)]
pub struct Product {
    #[key]
    #[auto]
    pub id: i64,
    
    pub name: String,
    
    pub description: String,
    
    pub price: Decimal,
    
    pub stock: i32,
    
    pub category_id: i64,
    
    pub is_active: bool,
    
    pub created_at: DateTime<Utc>,
    
    pub updated_at: DateTime<Utc>,
    
    #[belongs_to(Category)]
    pub category: BelongsTo<Category>,
    
    #[has_many(OrderItem)]
    pub order_items: HasMany<OrderItem>,
    
    #[has_many(Review)]
    pub reviews: HasMany<Review>,
}

#[derive(Debug, Schema)]
#[toasty(schema = "categories", driver = Pg)]
pub struct Category {
    #[key]
    #[auto]
    pub id: i64,
    
    pub name: String,
    
    pub slug: String,
    
    pub parent_id: Option<i64>,
    
    #[has_many(Product)]
    pub products: HasMany<Product>,
}

impl Category {
    pub fn find_root_categories(db: &mut Db) -> impl Future<Output = Result<Vec<Self>, toasty::Error> {
        Self::find()
            .filter(Self::parent_id.is_null())
            .filter(Self::id.gt(0))  // 排除root
            .order_by(Self::name)
            .all(db)
    }
}

// ============ 订单系统 ============

#[derive(Debug, EnumDisplay, EnumFromDb)]
pub enum OrderStatus {
    Draft,
    Pending,
    Paid,
    Processing,
    Shipped,
    Delivered,
    Cancelled,
    Refunded,
}

#[derive(Debug, Schema)]
#[toasty(schema = "orders", driver = Pg)]
pub struct Order {
    #[key]
    #[auto]
    pub id: i64,
    
    pub user_id: i64,
    
    pub status: OrderStatus,
    
    pub total: Decimal,
    
    pub shipping_address: Json<ShippingAddress>,
    
    pub notes: Option<String>,
    
    pub created_at: DateTime<Utc>,
    
    pub updated_at: DateTime<Utc>,
    
    #[belongs_to(User)]
    pub user: BelongsTo<User>,
    
    #[has_many(OrderItem)]
    pub items: HasMany<OrderItem>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ShippingAddress {
    pub receiver: String,
    pub phone: String,
    pub province: String,
    pub city: String,
    pub district: String,
    pub detail: String,
    pub zip: Option<String>,
}

#[derive(Debug, Schema)]
#[toasty(schema = "order_items", driver = Pg)]
pub struct OrderItem {
    #[key]
    #[auto]
    pub id: i64,
    
    pub order_id: i64,
    
    pub product_id: i64,
    
    pub quantity: i32,
    
    pub unit_price: Decimal,
    
    pub subtotal: Decimal,
    
    #[belongs_to(Order)]
    pub order: BelongsTo<Order>,
    
    #[belongs_to(Product)]
    pub product: BelongsTo<Product>,
}

// ============ 评价系统 ============

#[derive(Debug, Schema)]
#[toasty(schema = "reviews", driver = Pg)]
pub struct Review {
    #[key]
    #[auto]
    pub id: i64,
    
    pub user_id: i64,
    
    pub product_id: i64,
    
    pub rating: i32,  // 1-5
    
    pub title: String,
    
    pub content: String,
    
    pub created_at: DateTime<Utc>,
    
    #[belongs_to(User)]
    pub user: BelongsTo<User>,
    
    #[belongs_to(Product)]
    pub product: BelongsTo<Product>,
}

impl Review {
    const MIN_RATING: i32 = 1;
    const MAX_RATING: i32 = 5;
    
    pub fn validate_rating(rating: i32) -> bool {
        rating >= Self::MIN_RATING && rating <= Self::MAX_RATING
    }
}

4.3 创建数据库和表结构

// src/db.rs

use toasty::driver::postgres::Pg;
use toasty::migrate::{Migration, Migrator};
use std::path::Path;

// 迁移定义
pub struct InitMigration;

#[toasty::migrate]
impl Migration for InitMigration {
    async fn up(db: &mut Pg) -> Result<(), toasty::Error> {
        // 用户表
        db.execute(r#"
            CREATE TABLE IF NOT EXISTS users (
                id BIGSERIAL PRIMARY KEY,
                username VARCHAR(50) UNIQUE NOT NULL,
                email VARCHAR(255) UNIQUE NOT NULL,
                password_hash VARCHAR(255) NOT NULL,
                is_active BOOLEAN DEFAULT TRUE,
                created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
                last_login_at TIMESTAMP WITH TIME ZONE
            )
        "#).await?;
        
        // 分类表
        db.execute(r#"
            CREATE TABLE IF NOT EXISTS categories (
                id BIGSERIAL PRIMARY KEY,
                name VARCHAR(100) NOT NULL,
                slug VARCHAR(100) UNIQUE NOT NULL,
                parent_id BIGINT REFERENCES categories(id)
            )
        "#).await?;
        
        // 商品表
        db.execute(r#"
            CREATE TABLE IF NOT EXISTS products (
                id BIGSERIAL PRIMARY KEY,
                name VARCHAR(255) NOT NULL,
                description TEXT,
                price DECIMAL(10, 2) NOT NULL,
                stock INT DEFAULT 0,
                category_id BIGINT REFERENCES categories(id),
                is_active BOOLEAN DEFAULT TRUE,
                created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
                updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
            )
        "#).await?;
        
        // 订单表
        db.execute(r#"
            CREATE TABLE IF NOT EXISTS orders (
                id BIGSERIAL PRIMARY KEY,
                user_id BIGINT NOT NULL REFERENCES users(id),
                status VARCHAR(20) DEFAULT 'Draft',
                total DECIMAL(10, 2) NOT NULL,
                shipping_address JSONB NOT NULL,
                notes TEXT,
                created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
                updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
            )
        "#).await?;
        
        // 订单项表
        db.execute(r#"
            CREATE TABLE IF NOT EXISTS order_items (
                id BIGSERIAL PRIMARY KEY,
                order_id BIGINT NOT NULL REFERENCES orders(id),
                product_id BIGINT NOT NULL REFERENCES products(id),
                quantity INT NOT NULL,
                unit_price DECIMAL(10, 2) NOT NULL,
                subtotal DECIMAL(10, 2) NOT NULL
            )
        "#).await?;
        
        // 评价表
        db.execute(r#"
            CREATE TABLE IF NOT EXISTS reviews (
                id BIGSERIC PRIMARY KEY,
                user_id BIGINT NOT NULL REFERENCES users(id),
                product_id BIGINT NOT NULL REFERENCES products(id),
                rating INT CHECK (rating >= 1 AND rating <= 5),
                title VARCHAR(255) NOT NULL,
                content TEXT NOT NULL,
                created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
            )
        "#).await?;
        
        // 索引
        db.execute("CREATE INDEX IF NOT EXISTS idx_products_category ON products(category_id)").await?;
        db.execute("CREATE INDEX IF NOT EXISTS idx_orders_user ON orders(user_id)").await?;
        db.execute("CREATE INDEX IF NOT EXISTS idx_order_items_order ON order_items(order_id)").await?;
        db.execute("CREATE INDEX IF NOT EXISTS idx_reviews_product ON reviews(product_id)").await?;
        
        Ok(())
    }
}

// 初始化数据库
pub async fn init_database(pool: &PgPool) -> Result<(), Box<dyn std::error::Error>> {
    let mut conn = pool.acquire().await?;
    InitMigration::up(&mut conn).await?;
    Ok(())
}

4.4 Service层实现

// src/services/order_service.rs

use crate::models::*;
use toasty::driver::postgres::PgPool;
use toasty::ops::*;
use std::sync::Arc;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum OrderError {
    #[error("Product not found: {0}")]
    ProductNotFound(i64),
    #[error("Insufficient stock for product: {0}")]
    InsufficientStock(i64),
    #[error("Invalid order status transition: {0} -> {1}")]
    InvalidStatusTransition(OrderStatus, OrderStatus),
    #[error("Database error: {0}")]
    Database(#[from] toasty::Error),
}

pub struct OrderService {
    db: PgPool,
}

impl OrderService {
    pub fn new(db: PgPool) -> Self {
        Self { db }
    }
    
    /// 创建订单(完整的事务实现)
    pub async fn create_order(
        &self,
        user_id: i64,
        items: Vec<CreateOrderItem>,
        shipping: ShippingAddress,
    ) -> Result<Order, OrderError> {
        let mut conn = self.db.acquire().await?;
        
        // 验证商品并计算总价
        let mut total = Decimal::ZERO;
        let mut validated_items = Vec::with_capacity(items.len());
        
        for item in items {
            let product = Product::get_by_id(&mut conn, item.product_id)
                .await?
                .ok_or(OrderError::ProductNotFound(item.product_id))?;
            
            if product.stock < item.quantity {
                return Err(OrderError::InsufficientStock(item.product_id));
            }
            
            let subtotal = product.price * Decimal::new(item.quantity as i64, 2);
            total = total + subtotal;
            
            validated_items.push((product, item.quantity, product.price, subtotal));
        }
        
        // 创建订单
        let order = Order::create()
            .set(Order {
                user_id,
                status: OrderStatus::Draft,
                total,
                shipping_address: serde_json::to_value(shipping).unwrap().into(),
                notes: None,
                created_at: chrono::Utc::now(),
                updated_at: chrono::Utc::now(),
                // 关系字段,会被忽略
                user: BelongsTo::default(),
                items: HasMany::default(),
            })
            .exec(&mut conn)
            .await?;
        
        // 创建订单项并扣减库存
        for (product, quantity, unit_price, subtotal) in validated_items {
            OrderItem::create()
                .set(OrderItem {
                    id: 0,  // auto
                    order_id: order.id,
                    product_id: product.id,
                    quantity,
                    unit_price,
                    subtotal,
                    order: BelongsTo::default(),
                    product: BelongsTo::default(),
                })
                .exec(&mut conn)
                .await?;
            
            // 扣库存
            Product::update()
                .set(Product {
                    stock: product.stock - quantity,
                    updated_at: chrono::Utc::now(),
                    ..product
                })
                .filter(Product::id.eq(product.id))
                .exec(&mut conn)
                .await?;
        }
        
        // 更新订单状态
        let order = Order::update()
            .set(Order {
                status: OrderStatus::Pending,
                updated_at: chrono::Utc::now(),
                ..order
            })
            .filter(Order::id.eq(order.id))
            .exec(&mut conn)
            .await?;
        
        Ok(order)
    }
    
    /// 获取用户订单列表(含详情)
    pub async fn get_user_orders(&self, user_id: i64) -> Result<Vec<Order>, OrderError> {
        let mut conn = self.db.acquire().await?;
        
        let orders = Order::find()
            .filter(Order::user_id.eq(user_id))
            .order_by_desc(Order::created_at)
            .expand(Order::items.with(Order::product))  // 预加载商品
            .all(&mut conn)
            .await?;
        
        Ok(orders)
    }
    
    /// 取消订单
    pub async fn cancel_order(&self, order_id: i64, user_id: i64) -> Result<Order, OrderError> {
        let mut conn = self.db.acquire().await?;
        
        // 检查订单状态
        let order = Order::get_by_id(&mut conn, order_id)
            .await?
            .ok_or_else(|| toasty::Error::NotFound)?; // 简化处理
        
        if order.user_id != user_id {
            return Err(OrderError::InvalidStatusTransition(order.status, OrderStatus::Cancelled)); // 简化
        }
        
        if !matches!(order.status, OrderStatus::Draft | OrderStatus::Pending) {
            return Err(OrderError::InvalidStatusTransition(order.status, OrderStatus::Cancelled));
        }
        
        // 恢复库存
        let items = OrderItem::find()
            .filter(OrderItem::order_id.eq(order_id))
            .expand(OrderItem::product)
            .all(&mut conn)
            .await?;
        
        for item in items {
            if let Some(product) = item.product {
                Product::update()
                    .set(Product {
                        stock: product.stock + item.quantity,
                        updated_at: chrono::Utc::now(),
                        ..product
                    })
                    .filter(Product::id.eq(product.id))
                    .exec(&mut conn)
                    .await?;
            }
        }
        
        // 更新状态
        let order = Order::update()
            .set(Order {
                status: OrderStatus::Cancelled,
                updated_at: chrono::Utc::now(),
                ..order
            })
            .filter(Order::id.eq(order_id))
            .exec(&mut conn)
            .await?;
        
        Ok(order)
    }
    
    /// 获取订单统计
    pub async fn get_order_stats(&self, user_id: i64) -> Result<OrderStats, OrderError> {
        let mut conn = self.db.acquire().await?;
        
        let total_orders = Order::find()
            .filter(Order::user_id.eq(user_id))
            .count(&mut conn)
            .await? as i64;
        
        let pending_orders = Order::find()
            .filter(Order::user_id.eq(user_id))
            .filter(Order::status.eq(OrderStatus::Pending))
            .count(&mut conn)
            .await? as i64;
        
        let total_spent = Order::find()
            .filter(Order::user_id.eq(user_id))
            .filter(Order::status.ne(OrderStatus::Cancelled))
            .filter(Order::status.ne(OrderStatus::Refunded))
            .select(Order::total.sum())
            .one(&mut conn)
            .await?
            .unwrap_or(Decimal::ZERO);
        
        Ok(OrderStats {
            total_orders,
            pending_orders,
            total_spent,
        })
    }
}

#[derive(Debug, Serialize)]
pub struct CreateOrderItem {
    pub product_id: i64,
    pub quantity: i32,
}

#[derive(Debug, Serialize)]
pub struct OrderStats {
    pub total_orders: i64,
    pub pending_orders: i64,
    pub total_spent: Decimal,
}

4.5 API层实现

// src/api/mod.rs

use crate::services::order_service::{OrderService, OrderError};
use crate::models::*;
use actix_web::{web, HttpResponse, Result};
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize)]
pub struct CreateOrderRequest {
    pub items: Vec<CreateOrderItem_api>,
    pub shipping_address: ShippingAddress_api,
}

#[derive(Debug, Deserialize)]
pub struct CreateOrderItem_api {
    pub product_id: i64,
    pub quantity: i32,
}

#[derive(Debug, Deserialize)]
pub struct ShippingAddress_api {
    pub receiver: String,
    pub phone: String,
    pub province: String,
    pub city: String,
    pub district: String,
    pub detail: String,
    pub zip: Option<String>,
}

impl From<ShippingAddress_api> for ShippingAddress {
    fn from(api: ShippingAddress_api) -> Self {
        ShippingAddress {
            receiver: api.receiver,
            phone: api.phone,
            province: api.province,
            city: api.city,
            district: api.district,
            detail: api.detail,
            zip: api.zip,
        }
    }
}

pub async fn create_order(
    service: web::Data<OrderService>,
    user_id: web::Path<i64>,
    req: web::Json<CreateOrderRequest>,
) -> Result<HttpResponse> {
    let items = req.items
        .iter()
        .map(|i| CreateOrderItem {
            product_id: i.product_id,
            quantity: i.quantity,
        })
        .collect();
    
    let shipping = req.shipping_address.clone().into();
    
    match service.create_order(*user_id, items, shipping).await {
        Ok(order) => Ok(HttpResponse::Ok().json(order)),
        Err(e) => Ok(HttpResponse::BadRequest().json(serde_json::json!({
            "error": e.to_string()
        }))),
    }
}

pub async fn list_orders(
    service: web::Data<OrderService>,
    user_id: web::Path<i64>,
) -> Result<HttpResponse> {
    match service.get_user_orders(*user_id).await {
        Ok(orders) => Ok(HttpResponse::Ok().json(orders)),
        Err(e) => Ok(HttpResponse::InternalServerError().json(serde_json::json!({
            "error": e.to_string()
        }))),
    }
}

五、性能优化与最佳实践

5.1 预加载 vs 懒加载

// ❌ 懒加载:N+1查询问题
let users = User::find().limit(100).all(&mut db).await?;
for user in &users {
    let orders = user.orders();  // 每次都触发新查询!
}

// ✅ 预加载:1次查询
let users = User::find()
    .limit(100)
    .expand(User::orders)  // 一次加载
    .all(&mut db)
    .await?;

// ✅ 条件预加载
let users = User::find()
    .expand_with(User::orders, |q| {
        q.filter(Order::status.eq(OrderStatus::Delivered))
            .order_by_desc(Order::created_at)
            .limit(10)
    })
    .all(&mut db)
    .await?;

5.2 分页查询

fn paginate<T: Model>(
    db: &mut Db,
    query: Select<T>,
    page: i64,
    per_page: i64,
) -> Result<PaginatedResult<T>, toasty::Error> {
    let total = query.clone().count(db).await?;
    let items = query
        .offset((page - 1) * per_page)
        .limit(per_page)
        .all(db)
        .await?;
    
    Ok(PaginatedResult {
        items,
        page,
        per_page,
        total,
        total_pages: (total + per_page - 1) / per_page,
    })
}

5.3 批量操作

// 批量插入
let users = vec![
    User { id: 0, username: "alice".into(), ... },
    User { id: 0, username: "bob".into(), ... },
    User { id: 0, username: "charlie".into(), ... },
];

users.into_iter()
    .map(User::create)
    .map(|op| op.exec(&mut db))
    .collect::<Vec<_>>()
// 或者用事务批量执行
let mut tx = db.begin().await?;
for user in users {
    User::create().set(user).exec(&mut tx).await?;
}
tx.commit().await?;

5.4 连接池配置

use toasty::driver::postgres::{PgPool, PgPoolOptions};

let pool = PgPoolOptions::new()
    .max_connections(20)           // 最大连接数
    .min_connections(5)             // 最小连接数
    .acquire_timeout(Duration::from_secs(10))
    .idle_timeout(Duration::from_secs(600))
    .max_lifetime(Duration::from_secs(1800))
    .connect("postgres://user:pass@localhost/mydb")
    .await?;

六、与现有技术的对比

6.1 Toasty vs SQLx

维度SQLxToasty
SQL风格纯SQL手写Rust表达式
类型安全编译时编译时 + 运行时
关系查询手写JOIN自动关联
学习成本
灵活性

6.2 Toasty vs SeaORM

维度SeaORMToasty
表达式风格自有DSLRust操作符
关系表达宏 + 关系原生关联
数据库支持7种4种+DynamoDB
Tokio集成可选深度集成

七、总结与展望

Toasty的出现代表了Rust ORM的一个新方向:不是做一个通用的SQL生成器,而是做一个真正为应用服务的查询引擎。它的核心理念值得我们学习:

  1. 利用Rust的类型系统:让编译器帮我们检查错误
  2. 尊重数据库特性:不抹平差异,而是充分利用每种数据库的长处
  3. 解耦应用schema与数据库schema:让模型定义更灵活
  4. 与Tokio深度集成:异步优先,一切皆async

2026年,随着Rust在云原生、后端领域的进一步普及,像Toasty这样的"应用级"框架会越来越受欢迎。它们不是在做"通用"的妥协,而是在各自的方向上做到极致。

对于Rust开发者来说,这是一个好消息:我们终于有一个可以称之为"好用"的异步ORM了。它可能不是最灵活的,但是最符合Rust哲学的。如果你正在用Rust构建后端服务,Toasty值得关注。

参考资源

  • 官方GitHub:https://github.com/tokio-rs/toasty
  • 官方文档:https://docs.rs/toasty
  • 博客:Toasty v1.0 Release Notes (2026.05)

推荐文章

Vue 3 路由守卫详解与实战
2024-11-17 04:39:17 +0800 CST
`Blob` 与 `File` 的关系
2025-05-11 23:45:58 +0800 CST
Golang Select 的使用及基本实现
2024-11-18 13:48:21 +0800 CST
如何实现生产环境代码加密
2024-11-18 14:19:35 +0800 CST
程序员茄子在线接单