nestjs with graphql - ch6 (transaction)

Jae Min·2023년 12월 13일
0

nestjs with graphql

목록 보기
6/6
post-custom-banner

3.0 이상의 typeorm 을 가지고 트랜잭션 관리를 해보자.
레퍼런스가 너무 많아서 그냥 기록용으로 작성하려고 한다.

// room.repository.ts
import { Injectable } from '@nestjs/common';
import { RoomEntity } from 'lib/database/entity';
import { DataSource, Repository } from 'typeorm';

@Injectable()
export class RoomRepository extends Repository<RoomEntity> {
  constructor(private dataSource: DataSource) {
    super(
      RoomEntity, 
      dataSource.createEntityManager(),
      dataSource.createQueryRunner(),
	);
  }

  async createRoom(name: string): Promise<RoomEntity> {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();
    try {
      const result = await queryRunner.manager.save(RoomEntity, { name: name });
      await queryRunner.commitTransaction();
      return result;
    } catch (error) {
      await queryRunner.rollbackTransaction();
    } finally {
      await queryRunner.release();
    }
  }
}

자세히 보면 생성자 부분을 잘 봐야 한다.
상속받은 Repository class 에

  • 접근하고자 하는 엔티티 (RoomEntity)
  • entityManager (dataSource.createEntityManager())
  • queryRunner (dataSource.createQueryRunner())

를 주입시켜줘서 사용한다.

위와 같이 작성하는것이 일반적인데, repo 단에서 로직 구현할떄 마다 저렇게 할 수는 없으니, 코드를 줄여보자.

상속받은 Repository class 에는 EntityManager 에 접근할 수 있는 manager 라는 멤버변수가 있다.

export declare class Repository<Entity extends ObjectLiteral> {
    /**
     * Entity target that is managed by this repository.
     * If this repository manages entity from schema,
     * then it returns a name of that schema instead.
     */
    readonly target: EntityTarget<Entity>;
    /**
     * Entity Manager used by this repository.
     */
    readonly manager: EntityManager;
    /**
     * Query runner provider used for this repository.
     */
    readonly queryRunner?: QueryRunner;
    /**
     * Entity metadata of the entity current repository manages.
     */
    get metadata(): import("..").EntityMetadata;
    constructor(target: EntityTarget<Entity>, manager: EntityManager, queryRunner?: QueryRunner);

manager 를 통해서 EntityManager class 에 접근해서 보면 transaction 함수가 보인다.

export declare class EntityManager {
    /**
     * Wraps given function execution (and all operations made there) in a transaction.
     * All database operations must be executed using provided entity manager.
     */
    transaction<T>(runInTransaction: (entityManager: EntityManager) => Promise<T>): Promise<T>;
    /**
     * Wraps given function execution (and all operations made there) in a transaction.
     * All database operations must be executed using provided entity manager.
     */
    transaction<T>(isolationLevel: IsolationLevel, runInTransaction: (entityManager: EntityManager) => Promise<T>): Promise<T>;

설명을 잘 읽어보면,
주어진 함수를 하나의 transaction 으로 감싸서 실행할 수 있다. 그리고 반드시 주어진 entityManager 를 이용해야 한다. 라고 나와있다.

이거를 사용하면 되겠다 생각해서, 다음과 같이 transaction 안에 실행시킬 함수의 구현체를 넣어주면 구현할 수 있다.
대신, transaction 안에서는 에러가 발생하면 unhandled error 로 처리되기 때문에 반드시 안에서 try ~ catch 구문으로 에러를 핸들링해줘야 한다.

try {
      await this.manager.transaction(async (entityManager) => {
        try {
          await entityManager.save(RoomEntity, { name: name });
        } catch (error) {
          throw new Error('duplicated key');
        }
      });
    } catch (error) {
      throw error;
    }

그래서 다음과 같이 구현해냈다.

// room.repository.ts
@Injectable()
export class RoomRepository extends Repository<RoomEntity> {
  constructor(private dataSource: DataSource) {
    super(
      RoomEntity,
      dataSource.createEntityManager(),
      dataSource.createQueryRunner(),
    );
  }

  async getAllRoomIds(): Promise<RoomEntity[]> {
    return await this.find();
  }

  async getRoom(id: string): Promise<RoomEntity> {
    return await this.findOne({
      where: {
        id: id,
      },
    });
  }

  async createRoom(name: any): Promise<void> {
    try {
      await this.manager.transaction(async (entityManager) => {
        try {
          await entityManager.save(RoomEntity, { name: name });
        } catch (error) {
          throw new Error('duplicated key');
        }
      });
    } catch (error) {
      throw error;
    }
  }
}

REF

사진: UnsplashYeik CD

profile
자유로워지고 싶다면 기록하라.
post-custom-banner

0개의 댓글