-
-
Notifications
You must be signed in to change notification settings - Fork 0
RAprogramm edited this page Jan 7, 2026
·
2 revisions
具有自动提交/回滚的类型安全多实体事务。
数据库事务是一种将多个数据库操作组合成单个原子单元的方法。这意味着:
- 全有或全无:要么所有操作都成功,要么没有任何操作被应用
- 自动回滚:如果任何操作失败,所有先前的更改都会自动撤销
- 数据一致性:您的数据库永远不会处于不一致状态
想象一下您正在构建一个银行应用程序,需要在账户之间转账:
1. 从账户A减去100元
2. 向账户B增加100元
没有事务,如果步骤1成功但步骤2失败(网络错误、数据库崩溃等),您刚刚丢失了100元!钱从A中减去了,但从未添加到B。
使用事务,如果步骤2失败,步骤1会自动回滚。钱留在账户A中,就像什么都没发生一样。
将transactions属性添加到您的实体:
use entity_derive::Entity; use uuid::Uuid; #[derive(Entity)] #[entity(table = "accounts", transactions)] // <- 添加这个 pub struct Account { #[id] pub id: Uuid, #[field(create, update, response)] pub user_id: Uuid, #[field(create, update, response)] pub balance: i64, }
对于带有#[entity(transactions)]的Account实体,宏会生成:
pub struct AccountTransactionRepo<'t> { tx: &'t mut sqlx::Transaction<'static, sqlx::Postgres>, }
这就像您的常规仓库一样,但所有操作都在事务内部发生。
pub trait TransactionWithAccount<'p> { fn with_accounts(self) -> Transaction<'p, PgPool, AccountTransactionRepo<'static>>; }
这将with_accounts()方法添加到事务构建器中。
在事务内部,您可以访问这些方法:
| 方法 | 签名 | 描述 |
|---|---|---|
create |
create(dto) -> Result<Entity, Error> |
插入新记录 |
find_by_id |
find_by_id(id) -> Result<Option<Entity>, Error> |
按主键查找 |
update |
update(id, dto) -> Result<Entity, Error> |
更新现有记录 |
delete |
delete(id) -> Result<bool, Error> |
删除记录(或软删除) |
list |
list(limit, offset) -> Result<Vec<Entity>, Error> |
分页列表 |
use entity_core::prelude::*; async fn create_account(pool: &PgPool, user_id: Uuid) -> Result<Account, AppError> { Transaction::new(pool) // 1. 开始构建事务 .with_accounts() // 2. 添加Account仓库 .run(|mut ctx| async move { // 3. 执行操作 let account = ctx.accounts().create(CreateAccountRequest { user_id, balance: 0, }).await?; Ok(account) // 4. 返回结果(自动提交) }) .await }
-
Transaction::new(pool)— 使用数据库池创建新的事务构建器 -
.with_accounts()— 将Account仓库添加到事务上下文 -
.run(|mut ctx| async move { ... })— 在事务内执行操作 -
Ok(account)— 返回Ok提交事务。返回Err回滚事务。
此示例展示了事务的全部功能:
use entity_core::prelude::*; use uuid::Uuid; #[derive(Debug)] pub enum TransferError { Database(sqlx::Error), AccountNotFound(Uuid), InsufficientFunds { available: i64, requested: i64 }, } impl From<sqlx::Error> for TransferError { fn from(e: sqlx::Error) -> Self { TransferError::Database(e) } } impl From<TransactionError<sqlx::Error>> for TransferError { fn from(e: TransactionError<sqlx::Error>) -> Self { TransferError::Database(e.into_inner()) } } /// 原子地在两个账户之间转账。 /// /// 如果任何步骤失败,所有更改都会自动回滚。 pub async fn transfer( pool: &PgPool, from_id: Uuid, to_id: Uuid, amount: i64, ) -> Result<(), TransferError> { Transaction::new(pool) .with_accounts() .run(|mut ctx| async move { // 步骤1:获取源账户 let from = ctx.accounts() .find_by_id(from_id) .await? .ok_or(TransferError::AccountNotFound(from_id))?; // 步骤2:检查是否有足够的钱 if from.balance < amount { return Err(TransferError::InsufficientFunds { available: from.balance, requested: amount, }); } // 步骤3:获取目标账户 let to = ctx.accounts() .find_by_id(to_id) .await? .ok_or(TransferError::AccountNotFound(to_id))?; // 步骤4:从源账户减去 // 如果这成功但步骤5失败,这将被回滚 ctx.accounts().update(from_id, UpdateAccountRequest { balance: Some(from.balance - amount), user_id: None, }).await?; // 步骤5:添加到目标账户 ctx.accounts().update(to_id, UpdateAccountRequest { balance: Some(to.balance + amount), user_id: None, }).await?; // 所有操作成功 - 事务将提交 Ok(()) }) .await }
| 场景 | 结果 |
|---|---|
| 两次更新都成功 | 事务提交,转账完成 |
| 源账户未找到 | 事务回滚(无更改) |
| 余额不足 | 事务回滚(无更改) |
| 第一次更新成功,第二次失败 | 事务回滚(第一次更新被撤销!) |
| 事务中途网络错误 | 事务回滚(无部分更改) |
您可以原子地操作多个实体:
#[derive(Entity)] #[entity(table = "accounts", transactions)] pub struct Account { #[id] pub id: Uuid, #[field(create, update, response)] pub balance: i64, } #[derive(Entity)] #[entity(table = "transfer_logs", transactions)] pub struct TransferLog { #[id] pub id: Uuid, #[field(create, response)] pub from_account_id: Uuid, #[field(create, response)] pub to_account_id: Uuid, #[field(create, response)] pub amount: i64, #[auto] #[field(response)] pub created_at: DateTime<Utc>, } async fn transfer_with_logging( pool: &PgPool, from_id: Uuid, to_id: Uuid, amount: i64, ) -> Result<TransferLog, AppError> { Transaction::new(pool) .with_accounts() // 添加Account仓库 .with_transfer_logs() // 添加TransferLog仓库 .run(|mut ctx| async move { // 更新余额 let from = ctx.accounts().find_by_id(from_id).await? .ok_or(AppError::NotFound)?; ctx.accounts().update(from_id, UpdateAccountRequest { balance: Some(from.balance - amount), }).await?; let to = ctx.accounts().find_by_id(to_id).await? .ok_or(AppError::NotFound)?; ctx.accounts().update(to_id, UpdateAccountRequest { balance: Some(to.balance + amount), }).await?; // 创建日志条目 - 全部在同一事务中! let log = ctx.transfer_logs().create(CreateTransferLogRequest { from_account_id: from_id, to_account_id: to_id, amount, }).await?; Ok(log) }) .await }
如果日志创建失败,两个账户更新都会回滚!
从闭包返回的任何错误都会触发回滚:
Transaction::new(pool) .with_accounts() .run(|mut ctx| async move { ctx.accounts().update(id, dto).await?; // 成功 // 某些验证失败 if amount < 0 { return Err(AppError::InvalidAmount); // <- 触发回滚! } // 这永远不会执行,上面的更新会被撤销 ctx.accounts().update(other_id, other_dto).await?; Ok(()) }) .await
TransactionError枚举告诉您出了什么问题:
use entity_core::transaction::TransactionError; let result = Transaction::new(pool) .with_accounts() .run(|mut ctx| async move { /* ... */ }) .await; match result { Ok(value) => { println!("成功:{:?}", value); } Err(e) => { if e.is_begin() { println!("启动事务失败"); } else if e.is_operation() { println!("操作失败:{}", e); } else if e.is_commit() { println!("提交失败"); } else if e.is_rollback() { println!("回滚失败"); } // 获取内部数据库错误 let db_error: sqlx::Error = e.into_inner(); } }
事务遵守soft_delete属性:
#[derive(Entity)] #[entity(table = "documents", transactions, soft_delete)] pub struct Document { #[id] pub id: Uuid, #[field(create, response)] pub title: String, #[field(skip)] pub deleted_at: Option<DateTime<Utc>>, // soft_delete必需 } async fn archive_document(pool: &PgPool, id: Uuid) -> Result<bool, AppError> { Transaction::new(pool) .with_documents() .run(|mut ctx| async move { // 这设置deleted_at = NOW()而不是DELETE let deleted = ctx.documents().delete(id).await?; Ok(deleted) }) .await }
错误示例:长时间运行的事务
Transaction::new(pool) .with_accounts() .run(|mut ctx| async move { let account = ctx.accounts().find_by_id(id).await?; // 不要这样做:在事务内调用外部API let rate = external_api.get_exchange_rate().await?; // <- 慢! ctx.accounts().update(id, dto).await?; Ok(()) }) .await
正确示例:在外部进行慢操作
// 在启动事务之前获取外部数据 let rate = external_api.get_exchange_rate().await?; Transaction::new(pool) .with_accounts() .run(|mut ctx| async move { ctx.accounts().update(id, UpdateAccountRequest { balance: Some(calculate_new_balance(rate)), }).await?; Ok(()) }) .await
不必要:
Transaction::new(pool) .with_users() .run(|mut ctx| async move { ctx.users().find_by_id(id).await // 只有一个操作! }) .await
更好:使用常规仓库
pool.find_by_id(id).await // 不需要事务
始终使用?传播错误:
Transaction::new(pool) .with_accounts() .run(|mut ctx| async move { let result = ctx.accounts().update(id, dto).await; // 不要这样做:吞掉错误 if let Err(e) = result { log::error!("更新失败:{}", e); // 事务不会正确回滚! } // 这样做:传播错误 ctx.accounts().update(id, dto).await?; // <- 使用? Ok(()) }) .await
Transaction::new(pool) .with_products() .run(|mut ctx| async move { let product = ctx.products().find_by_id(id).await? .ok_or(AppError::NotFound)?; if product.stock < quantity { return Err(AppError::OutOfStock); } ctx.products().update(id, UpdateProductRequest { stock: Some(product.stock - quantity), ..Default::default() }).await?; Ok(product) }) .await
Transaction::new(pool) .with_orders() .with_order_items() .run(|mut ctx| async move { // 创建父记录 let order = ctx.orders().create(CreateOrderRequest { customer_id, status: "pending".to_string(), }).await?; // 创建子记录 for item in items { ctx.order_items().create(CreateOrderItemRequest { order_id: order.id, product_id: item.product_id, quantity: item.quantity, }).await?; } Ok(order) }) .await
🇬🇧 English | 🇷🇺 Русский | 🇰🇷 한국어 | 🇪🇸 Español | 🇨🇳 中文
Getting Started
Features
Advanced
Начало работы
Возможности
Продвинутое
시작하기
기능
고급
Comenzando
Características
Avanzado
入门
功能
高级