事务
自 v0.6.4 起可用
虽然批量操作可以涵盖大多数需要一起成功或失败的查询用例,但它不允许您在每个查询之间执行代码。相反,您可以使用 PrismaClient::_transaction
,它提供了基于闭包和手动执行事务内各个查询和任意代码的方法。
这两种方法都提供了提交和回滚事务的能力,并生成一个专用的 PrismaClient
实例,必须在执行事务时使用。
事务闭包
在闭包中运行您的事务是官方 Prisma 客户端 (在新标签页中打开) 使用的方法。它可能很好,因为所有事务代码都可以在一个地方保存,但缺点是闭包可能难以使用。
要以这种方式执行事务,只需调用 client._transaction().run(..)
并提供一个返回 async move
块到 run()
。闭包应接受一个参数(专用的 PrismaClient
实例),并返回一个 Result
。
如果闭包返回 Ok
,则事务将尝试提交自身,如果它返回 Err
,它将尝试回滚。
let (user, post) = client
._transaction()
.run(|client| async move {
let user = client
.user()
.create("brendan".to_string(), vec![])
.exec()
.await?;
client
.post()
.create(
"test".to_string(),
true,
vec![post::author::connect(
user::id::equals(user.id.clone())
)],
)
.exec()
.await
// if query succeeds, return user + post from transaction
.map(|post| (user, post))
})
.await?;
错误类型
事务闭包必须返回一个 Result
,但 Err
泛型可以是几乎任何东西,并且 Ok
泛型根本不受限制。
为了允许在事务闭包内使用 ?
,错误类型必须实现 From<prisma_client_rust::QueryError>
(如果不需要自定义错误类型,则包括 QueryError
本身)。这可以通过手动实现完成
use prisma_client_rust::QueryError;
enum CustomError {
QueryError(QueryError)
}
impl From<QueryError> for CustomError {
fn from(e: QueryError) { ... }
}
或通过像 thiserror
(在新标签页中打开) 这样的库及其 #[from]
属性完成
#[derive(thiserror::Error)]
enum CustomError {
#[error("Database error occurred")]
QueryError(prisma_client_rust::QueryError),
...
}
指定错误类型
- 直接使用泛型参数。这可以工作,但需要
_
用于run
的其余泛型参数,这可能不是理想的。
cilent
._transaction()
.run::<CustomError, _, _, _>(..)
.await?;
- 类型转换。如果您的闭包返回
Ok
,您可以将其转换为具有适当错误类型的Result
。
client
._transaction()
.run(|client| async move {
let user = client
.user()
.create("brendan".to_string(), vec![])
.exec()
.await?;
Ok(user) as Result<_, CustomError>;
})
.await?
- 返回查询的
Result
- 这可能是最漂亮的解决方案。如果您使用自定义错误类型,请在await
后使用map_err
将QueryError
转换为您的自定义错误类型。
client
._transaction()
.run(|client| async move {
client
.user()
.create("brendan".to_string(), vec![])
.exec()
// No `?` so that `Result` with error type is returned
.await
})
.await?
手动事务
如果您希望手动控制事务何时提交和回滚,请使用 client._transaction().begin()
不仅获取一个专用的 PrismaClient
,还可以获取一个 TransactionManger
实例,您可以使用它进行 commit
和 rollback
let (tx, client) = client
._transaction()
.begin()
.await?;
以上示例将客户端实例命名为client
,这意味着它会覆盖它创建的原始客户端,使其无法访问。您可以为客户端实例命名为tx_client
,或者将所有事务逻辑放在一个代码块中,以便原始client
变量不会在代码的其余部分被覆盖。
commit
和 rollback
使用由 begin
创建的客户端作为其唯一参数。这样做是因为这些函数需要对客户端执行操作,并且作为一项额外的预防措施,以防止在事务完成后使用特定于事务的客户端。
tx.commit(client).await?;
// or
tx.rollback(client).await?;
错误处理
使用此方法处理错误时必须小心。简单地使用 ?
可能会导致您的代码在运行 commit
或 rollback
之前返回。避免这种情况的一种简单方法是将您的事务逻辑放在一个函数中,在该函数中安全地使用 ?
,然后根据函数的结果 commit
或 rollback
。
let (tx, client) = client
._transaction()
.begin()
.await?;
async fn do_stuff(client: &PrismaClient) -> ... {
let user = client
.user()
.create("brendan".to_string(), vec![])
.exec()
.await?; // Early return won't escape transaction
...
}
// This is very similar to the closure method's internals
let result = match do_stuff(client).await {
Ok(v) => {
tx.commit(client).await?;
Ok(v)
},
Err(e) => {
tx.rollback(client).await?;
Err(e)
}
};