带有 @NestJs/mongoose 的 MongoDB 事务无法正常工作

Zen*_*ixo 7 mongoose mongodb nestjs

我真的需要你的帮助。我与 @NestJs/mongoose 的 MongoDB 事务不起作用...当我的条带支付失败时回滚不起作用...不过,我的订单集合保存了数据...我该如何解决这个问题..?

  async create(orderData: CreateOrderServiceDto): Promise<any> {
    const session = await this.connection.startSession();
    session.startTransaction();
    try {
      const createOrder = new this.orderModel(orderData);
      const order = await createOrder.save();

      await this.stripeService.charge(
        orderData.amount,
        orderData.paymentMethodId,
        orderData.stripeCustomerId,
      );
      await session.commitTransaction();
      return order;
    } catch (error) {
      await session.abortTransaction();
      throw error;
    } finally {
      await session.endSession();
    }
  }
Run Code Online (Sandbox Code Playgroud)

Kha*_*euh 7

我遇到了同样的问题,我在 github 上发现了这个问题:Mongo DB Transactions With Mongoose & Nestjs

所以我认为,根据这个问题,你必须调用create模型的方法,如下所示:

const order = await this.orderModel.create(orderData, { session });
Run Code Online (Sandbox Code Playgroud)

如您所见,该Model.create方法有一个重载作为SaveOptions参数:

create(docs: (AnyKeys<T> | AnyObject)[], options?: SaveOptions): Promise<HydratedDocument<T, TMethodsAndOverrides, TVirtuals>[]>;
Run Code Online (Sandbox Code Playgroud)

它需要一个SaveOptions可以包含会话的可选参数:

interface SaveOptions {
  checkKeys?: boolean;
  j?: boolean;
  safe?: boolean | WriteConcern;
  session?: ClientSession | null;
  timestamps?: boolean;
  validateBeforeSave?: boolean;
  validateModifiedOnly?: boolean;
  w?: number | string;
  wtimeout?: number;
}
Run Code Online (Sandbox Code Playgroud)

请注意,Model.save()也可以带SaveOptions参数。所以你也可以像你那样做:

const createOrder = new this.orderModel(orderData);
const order = await createOrder.save({ session });
Run Code Online (Sandbox Code Playgroud)

再远一点...

当我做很多需要事务的事情时,我想出了这个助手来避免许多代码重复:

import { InternalServerErrorException } from "@nestjs/common"
import { Connection, ClientSession } from "mongoose"

export const mongooseTransactionHandler = async <T = any>(
  method: (session: ClientSession) => Promise<T>,
  onError: (error: any) => any,
  connection: Connection, session?: ClientSession
): Promise<T> => {
  const isSessionFurnished = session === undefined ? false : true
  if (isSessionFurnished === false) {
    session = await connection.startSession()
    session.startTransaction()
  }

  let error
  let result: T
  try {
    result = await method(session)

    if (isSessionFurnished === false) {
      await session.commitTransaction()
    }
  } catch (err) {
    error = err
    if (isSessionFurnished === false) {
      await session.abortTransaction()
    }
  } finally {
    if (isSessionFurnished === false) {
      await session.endSession()
    }

    if (error) {
      onError(error)
    }

    return result
  }
}
Run Code Online (Sandbox Code Playgroud)

细节

可选参数session是为了防止您正在执行嵌套的嵌套事务。这就是为什么我检查是否提供了会话。如果是,则意味着我们处于嵌套事务中。因此,我们将让主事务提交、中止并结束会话。

例子

例如:你删除了一个User模特,然后用户的头像也是一个File模特。

/** UserService **/
async deleteById(id: string): Promise<void> {
  const transactionHandlerMethod = async (session: ClientSession): Promise<void> => {
    const user = await this.userModel.findOneAndDelete(id, { session })
    await this.fileService.deleteById(user.avatar._id.toString(), session)
  }

  const onError = (error: any) => {
    throw error
  }

  await mongooseTransactionHandler<void>(
    transactionHandlerMethod,
    onError,
    this.connection
  )
}

/** FileService **/
async deleteById(id: string, session?: ClientSession): Promise<void> {
  const transactionHandlerMethod = async (session: ClientSession): Promise<void> => {
    await this.fileModel.findOneAndRemove(id, { session })
  }

  const onError = (error: any) => {
    throw error
  }

  await mongooseTransactionHandler<void>(
    transactionHandlerMethod,
    onError,
    this.connection,
    session
  )
}
Run Code Online (Sandbox Code Playgroud)

所以,简而言之:

你可以这样使用它:

async create(orderData: CreateOrderServiceDto): Promise<any> {
  const transactionHandlerMethod = async (session: ClientSession): Promise<Order> => {
    const createOrder = new this.orderModel(orderData);
    const order = await createOrder.save({ session });

    await this.stripeService.charge(
      orderData.amount,
      orderData.paymentMethodId,
      orderData.stripeCustomerId,
    );

    return order
  }

  const onError = (error: any): void => {
    throw error
  }

  const order = await mongooseTransactionHandler<Order>(
    transactionHandlerMethod,
    onError,
    this.connection
  )

  return order
}
Run Code Online (Sandbox Code Playgroud)

希望它会有所帮助。

编辑

不要在嵌套事务中滥用model.save({ session })同一模型。由于某些原因,模型更新次数过多会引发错误。

为了避免这种情况,最好使用模型嵌入方法来更新并返回模型的新实例(model.findOneAndUpdate例如)。

  • 我曾经有过的最好的解释...谢谢你。❤️非常感谢你的时间...这真的帮助了我。❤️ (2认同)