Skip to content

Navigation Menu

Sign in
Sign up
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实体,宏会生成:

1. 事务仓库适配器

pub struct AccountTransactionRepo<'t> {
 tx: &'t mut sqlx::Transaction<'static, sqlx::Postgres>,
}

这就像您的常规仓库一样,但所有操作都在事务内部发生。

2. 构建器扩展特征

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
}

逐步说明:

  1. Transaction::new(pool) — 使用数据库池创建新的事务构建器
  2. .with_accounts() — 将Account仓库添加到事务上下文
  3. .run(|mut ctx| async move { ... }) — 在事务内执行操作
  4. 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
}

最佳实践

1. 保持事务简短

错误示例:长时间运行的事务

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

2. 不要为单个操作使用事务

不必要:

Transaction::new(pool)
 .with_users()
 .run(|mut ctx| async move {
 ctx.users().find_by_id(id).await // 只有一个操作!
 })
 .await

更好:使用常规仓库

pool.find_by_id(id).await // 不需要事务

3. 正确处理所有错误

始终使用?传播错误:

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

另请参阅

  • 属性 — 完整的属性文档
  • 钩子 — 在操作前/后运行代码
  • 命令 — CQRS命令模式
  • 事件 — 跟踪实体变更

🌐 Language

🇬🇧 English | 🇷🇺 Русский | 🇰🇷 한국어 | 🇪🇸 Español | 🇨🇳 中文


🇬🇧 English

Home

Getting Started

Features

Advanced


🇷🇺 Русский

Главная

Начало работы

Возможности

Продвинутое


🇰🇷 한국어

시작하기

기능

고급


🇪🇸 Español

Inicio

Comenzando

Características

Avanzado


🇨🇳 中文

首页

入门

功能

高级


📚 Reference

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /