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?;
问题在哪?
- SQL与代码分离:维护困难,改动时容易出错
- 类型安全丢失:手动映射,容易对不上
- 数据库特性被忽视:每个数据库都有自己的特性,但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?;
问题:
- 学习成本高:需要学习一套新的DSL
- 灵活性与复杂性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
| 维度 | SQLx | Toasty |
|---|---|---|
| SQL风格 | 纯SQL手写 | Rust表达式 |
| 类型安全 | 编译时 | 编译时 + 运行时 |
| 关系查询 | 手写JOIN | 自动关联 |
| 学习成本 | 低 | 中 |
| 灵活性 | 高 | 中 |
6.2 Toasty vs SeaORM
| 维度 | SeaORM | Toasty |
|---|---|---|
| 表达式风格 | 自有DSL | Rust操作符 |
| 关系表达 | 宏 + 关系 | 原生关联 |
| 数据库支持 | 7种 | 4种+DynamoDB |
| Tokio集成 | 可选 | 深度集成 |
七、总结与展望
Toasty的出现代表了Rust ORM的一个新方向:不是做一个通用的SQL生成器,而是做一个真正为应用服务的查询引擎。它的核心理念值得我们学习:
- 利用Rust的类型系统:让编译器帮我们检查错误
- 尊重数据库特性:不抹平差异,而是充分利用每种数据库的长处
- 解耦应用schema与数据库schema:让模型定义更灵活
- 与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)