额外
事务

事务

自 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),
		...
}

指定错误类型

  1. 直接使用泛型参数。这可以工作,但需要 _ 用于 run 的其余泛型参数,这可能不是理想的。
cilent
		._transaction()
		.run::<CustomError, _, _, _>(..)
		.await?;
  1. 类型转换。如果您的闭包返回 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?
  1. 返回查询的 Result - 这可能是最漂亮的解决方案。如果您使用自定义错误类型,请在 await 后使用 map_errQueryError 转换为您的自定义错误类型。
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 实例,您可以使用它进行 commitrollback

let (tx, client) = client
		._transaction()
		.begin()
		.await?;

以上示例将客户端实例命名为client,这意味着它会覆盖它创建的原始客户端,使其无法访问。您可以为客户端实例命名为tx_client,或者将所有事务逻辑放在一个代码块中,以便原始client变量不会在代码的其余部分被覆盖。

commitrollback 使用由 begin 创建的客户端作为其唯一参数。这样做是因为这些函数需要对客户端执行操作,并且作为一项额外的预防措施,以防止在事务完成后使用特定于事务的客户端。

tx.commit(client).await?;
// or 
tx.rollback(client).await?;

错误处理

使用此方法处理错误时必须小心。简单地使用 ? 可能会导致您的代码在运行 commitrollback 之前返回。避免这种情况的一种简单方法是将您的事务逻辑放在一个函数中,在该函数中安全地使用 ?,然后根据函数的结果 commitrollback

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)
		}
};