【问题标题】:nestjs / TypeOrm database transactionnestjs / TypeOrm 数据库事务
【发布时间】:2019-05-09 21:03:19
【问题描述】:

假设我们有 2 个服务,A 和 B。 服务 A 具有执行以下操作的功能:

  1. 验证数据
  2. 调用服务 B 函数,对数据库进行更改
  3. 做更多的事情
  4. 对数据库进行更改

现在,我们假设以下步骤 3 或 4 之一失败。 由于服务 B 在数据库中进行了更改,因此这些更改仍然存在。

在这种情况下有没有办法回滚数据库?我虽然关于数据库事务,但我在嵌套 js 中找不到任何方法,虽然它受 TypeOrm 支持,但嵌套看起来并不自然。 如果没有,我现在“卡住”了服务 B 发生的更改,但没有更改应该由 A 发生。

非常感谢。

【问题讨论】:

    标签: nestjs typeorm


    【解决方案1】:

    解决方案很多,应该都是基于SQL事务管理的。

    我个人认为,实现这一目标的最简单方法是在数据库上执行代码时使用相同的EntityManager 实例。然后你可以使用类似的东西:

    getConnection().transaction(entityManager -> {
        service1.doStuff1(entityManager);
        service2.doStuff2(entityManager);
    });
    

    您可以从EntityManager 实例中生成QueryRunner,该实例将包装在同一事务中,以防您在 ORM 操作之外执行原始 SQL。您还需要从EntityManager 生成Repository 实例,否则它们将在主事务之外执行代码。

    【讨论】:

    • 虽然这是一个解决方案,但有一个明显的问题:每个服务方法都必须接受一个 EntityManager 参数。如果这些服务使用其他可注入对象,例如数据访问对象 (DAO),那么服务方法将需要将 EntityManager 实例进一步向下传递(例如,传递给 DAO 方法)。我认为这是一个显而易见且冲动的解决方案——一个有效的解决方案!——但它添加了大量的样板代码,并且不适合 DI 和 IoC。
    • @avejidah 您在 TypeScript 节点中没有 ThreadLocal,因此您无法存储上下文实例,您可以在其中保留对所有服务的实体管理器的引用。单例也不起作用,因为每个请求都有一个实体管理器(由于事件队列,许多请求在节点中一起处理)。保持实体管理器无处不在的唯一方法是以某种方式将其存储在 NestJS 的执行上下文中,但您会失去手动实体管理器的使用和可能需要的手动事务管理。
    • 我个人保留参数并在方法中使用装饰器来注入一个新的实体管理器(如果它为空),所以我不必担心初始化它。保持明确的事务管理对我来说比代码糖更重要,我个人并不觉得它是样板。
    • @zenbeni 你能分享一个代码 sn-p 吗?你用的是哪个装饰器?也许@TransactionManager
    • 如果我使用nestjsx/crud有什么建议
    【解决方案2】:

    这是我解决它的方法,因为我需要使用悲观锁。

    我觉得这是“嵌套”的做事方式,因为您可以简单地要求 NestJS 注入一个 Typeorm Connection 的实例,然后您就可以开始了。

    @Injectable()
    class MyService {
      // 1. Inject the Typeorm Connection
      constructor(@InjectConnection() private connection: Connection) { }
    
      async findById(id: number): Promise<Thing> {
        return new Promise(resolve => {
          // 2. Do your business logic
          this.connection.transaction(async entityManager => {
            resolve(
              await entityManager.findOne(Thing, id, {
                lock: { mode: 'pessimistic_write' },
              }),
            );
          });
        });
      }
    }
    

    只需在 .transaction 块中放置您需要的任何其他逻辑,您就可以开始了。

    注意:您必须使用.transaction 方法提供的entityManager,否则它将不起作用。

    【讨论】:

    • 谢谢!我不知道如何注入连接
    【解决方案3】:

    typeorm-transactional-cls-hooked 使用 CLS(连续本地存储)在不同的存储库和服务方法之间处理和传播事务。

    @Injectable()
    export class PostService {
      constructor(
        private readonly authorRepository: AuthorRepository,
        private readonly postRepository: PostRepository,
      ) {}
    
      @Transactional() // will open a transaction if one doesn't already exist
      async createPost(authorUsername: string, message: string): Promise<Post> {
        const author = await this.authorRepository.create({ username: authorUsername });
        return this.postRepository.save({ message, author_id: author.id });
      }
    }
    

    【讨论】:

      【解决方案4】:

      在这种情况下,您必须对两个数据库操作使用相同的事务管理器。不幸的是,我没有示例存储库,但我找到了在 Node 中使用连续本地存储 (CLS) 的潜在解决方案:

      https://github.com/typeorm/typeorm/issues/1895

      这适用于 Express.js,但您可以创建 TransactionManager 的实例(例如,在嵌套中间件中)并将其存储在每个请求上下文中。然后,您将能够在您的服务方法调用中重复使用此事务管理器,前提是它们在上面的链接中使用 @Transaction 装饰器实现进行了注释。

      如果您的函数链中没有错误,事务管理器将提交所做的所有更改。否则,经理将回滚任何更改。

      希望这会有所帮助!

      【讨论】:

        猜你喜欢
        • 2020-07-02
        • 1970-01-01
        • 2020-04-25
        • 1970-01-01
        • 2019-01-30
        • 1970-01-01
        • 2019-11-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多