如何在NestJs和TypeORM中正确处理事务?

4
我正在使用NestJS和TypeORM。我有几个模块,每个模块都包含服务。有些模块导入其他模块并使用它们的服务。
// In Person Module
@Injectable()
class UserService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>
  ) {}

  public async deleteUsersByCompany(companyId: string) {
    const usersToDelete = await this.usersRepository
      .createQueryBuilder('user')
      .where('user.companyId = :companyId', { companyId })
      .getMany();

    if (usersToDelete.length > 0) {
      await this.usersRepository.delete(
        usersToDelete.map((user) => user.id),
      );
    }
  }

  ...
}

// Company Module

@Injectable()
class CompanyService {
  constructor(
    @InjectRepository(Company)
    private companysRepository: Repository<Company>,
    private usersService: UserService
  ) {}

  public async deleteCompany(companyId: string) {
    const companyToDelete = await this.companysRepository.find(companyId);

    if (companyToDelete) {
      await usersService.deleteUsersByCompany(companyToDelete.id)
      await this.companysRepository.delete(companyToDelete);
    }
  }

  ...
}

在这个例子中:
1. 我希望usersService.deleteUsersByCompany是原子操作 2. 我希望companyService.deleteCompany是原子操作 3. usersService.deleteUsersByCompanycompanyService.deleteCompany都可以由用户触发
我想将这两个操作都包装在事务中。我已经阅读了NestJS文档TypeOrm文档,但无法找到如何正确执行此操作的答案。
关键在于usersService.deleteUsersByCompany是由companyService.deleteCompany调用的。
1. 如何支持这样的“嵌套事务”? 2. QueryRunner是否替代了Repository模式? 3. 我的服务应该持有Repository和Connection实例吗?Repository用于非事务操作,连接用于获取QueryRunner?

你需要这些方法在事务上下文之外也能够执行吗? - noam steiner
是的,这些方法可以在任何地方使用。 - Naor
我有知识但没有时间,我会尝试明天写一个答案。同时 - 你需要做的是将可选参数传递给方法:entityManager,你可以从你选择的事务方法中获取它(包括所有类型的 TypeORM API,包括装饰器)deleteUsersByCompany(companyId: string, entityManager: EntityManager = this.usersRepository.manager) 在方法内部:entityManager.getRepository(User).createQueryBuilder('user') ... 应该是这样的,我会考虑更漂亮的实现方式。 - noam steiner
我已经知道了。问题是重构所有服务以获取另一个entityManager参数对我来说看起来不太好。这只是处理数据库的任何方法。此外,在每种方法中,我都必须检查事务是否已启动或者我应该启动一个新事务。对于任何方法来说,这都是很多样板文件。我尝试了 typeorm-transactional-cls-hooked,听起来很有前途,但它并不好用(https://github.com/odavid/typeorm-transactional-cls-hooked/issues/95),而且会引起其他问题。我仍在寻找合适的解决方案。 - Naor
我认为我给了你一个适当且可行的解决方案。如果你正在寻找抽象化,我建议阅读自定义存储库的文档。无论哪种方式,你都需要使用一个包或者自己实现那个逻辑。 - noam steiner
显示剩余2条评论
1个回答

0

使用事务的最简单方法是使用EntityManager API,可以从微服务中检查一个简单的示例:

import { WRITE_CONNECTION } from '@my-api/common';
import { Injectable, Logger } from '@nestjs/common';
import { RpcException } from '@nestjs/microservices';
import { InjectEntityManager } from '@nestjs/typeorm';
import { EntityManager } from 'typeorm';

@Injectable()
export class MyService {
  private logger = new Logger(MyService.name);
  constructor(
    @InjectEntityManager(WRITE_CONNECTION) private entityManager: EntityManager,
  ) {}

  async saveSomething(data: string): Promise<void> {
    try {
      return await this.entityManager.transaction(async (entityManager) => {
        const firstRepository = entityManager.getCustomRepository(FirstRepository);
        const secondRepository = entityManager.getCustomRepository(SecondRepository);

        const firstRecord = firstRepository.create({ data });
        await firstRepository.save(firstRecord);

        const secondRecord = secondRepository.create({ data });
        await secondRepository.save(secondRecord);

        // Save entities directly
        await entityManager.save([...]);
      });
    } catch (error) {
      this.logger.error(`Failed saving something, error ${error.message}`, error.stack);
      throw new RpcException(error.message);
    }
  }
}

正如您所看到的,我们在同一事务中使用entityManager进行读写操作,如果其中任何一个操作失败,它可以自动执行回滚。

有关更多详细信息,请查看TypeORM存储库,其中包含示例此处

因此,如果您想将两个操作包装在同一事务中,您需要调用一个方法并将存储库作为参数传递。例如:

async findOneItem(
  itemId: number,
  myRepository: MyRepository = this.myRepository, // Optional parameter if needed
) {
  return await myRepository.findOne(itemId);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接