Nestjs - QueryBuilder vs Repository

atesi·2023년 3월 13일
0

TypeORM에서는 데이터베이스에 접근하는 두가지 주요 방법으로 QueryBuilderRepository를 사용하는데 차이점을 비교해보자.

QueryBuilder vs Repository

  1. RepositoryEntity에 설정된 fetch type을 가지고 관계를 가져오고 QueryBuilder는 명시적으로 지정하지 않으면 기본 테이블만 가져온다.
  2. QueryBuilderRepository보다 SQL에 더 가깝기 때문에 더 복잡하거나 더 많은 SQL을 다루는 작업에 적합하다.
  3. 그러나 Repository 메서드 내에서 더 복잡한 쿼리를 생성하기 위해 QueryBuilder를 사용할 수 있으므로 두 가지 접근 방식이 상호 배타적이지 않다.

Setup

세가지 가정을 세우고 하나씩 증명하며 진행해 보자.

async findRoomUsingRepository(text: string): Promise<RoomEntity[]> {
    return await this.roomRepository.find({
      where: {
        title: Like(`%${text}%`),
      },
    })

방의 제목을 검색하기 위해 Repositoryfind 메소드를 이용해 작성을 했다. 안정성과 가독성 측면에서 우위를 보일 듯 하다.

@ManyToOne(() => UserEntity, (user) => user.room, {
	eager: true 
  })

@OneToMany(() => UserAuthority, (userAuthority) => userAuthority.user, {
    eager: true,
  })

기본적으로 TypeORM에서는 fetchType을 지정해 주지 않으면 Eager 혹은 Lazy도 적용되지 않는다. 확인을 위해 모두 Eager로 설정해 주었다.

SELECT ... FROM "rooms" "RoomEntity" LEFT JOIN "user" "RoomEntity_user" ON "RoomEntity_user"."id"="RoomEntity"."user_id"  LEFT JOIN "user_authority" "RoomEntity_user_authorities" ON "RoomEntity_user_authorities"."user_id"="RoomEntity_user"."id" WHERE ("RoomEntity"."title" LIKE $1) -- PARAMETERS: ["%hi%"]

따로 Join을 해주지 않아도 Eager Loding을 해오는 모습이다. Room 테이블에 연결된 user 테이블, user 테이블에 연결된 authorities 테이블을 Join해주는 것을 확인.

async findRoomUsingQueryBuilder(text: string): Promise<RoomEntity[]> {
    return await this.dataSource
      .createQueryBuilder()
      .select("room")
      .from(RoomEntity, "room")
      .leftJoinAndSelect('room.user', 'user')
      .leftJoinAndSelect('user.authorities', 'authorities')
      .where('room.title LIKE :title', { title: `%${title}%` })
	  .orderBy('room.createdAt', 'DESC')
      .getMany();
  }
}

QueryBuilder를 이용해 작성해 보았다. 비슷한 결과를 보여주기 위해 조건들을 직접 명시 해주어야 한다.

SELECT ... FROM "rooms" "room" LEFT JOIN "user" "user" ON "user"."id"="room"."user_id"  LEFT JOIN "user_authority" "authorities" ON "authorities"."user_id"="user"."id" WHERE "room"."title" LIKE $1 ORDER BY "room"."created_at" DESC -- PARAMETERS: ["%hi%"]

다음으로 넘어가보자.

async getUsersByRole(text: string): Promise<RoomEntity[]> {
    const usersByRole = await this.roomRepository
      .createQueryBuilder('room')
      .leftJoinAndSelect('room.user', 'user')
      .where('user.username = :text', { text })
      // .where('user.username = :text OR room.description LIKE :description', {
      //   text,
      //   description: `%${text}%`,
      // })
      .getMany();

    const usersByUsername = await this.roomRepository.find({
      where: {
        description: Like(`%${text}%`),
      },
    });

    const allUsers = [
      ...usersByRole, ...usersByUsername
    ];

    return allUsers;
  }

두가지 방식을 결합해서 이용해 보았다. 주석처리한 부분은 레포지토리를 이용해 결합한 부분을 쿼리빌더만을 이용해 처리한 모습이다. QueryBuilder 혹은 Repository만을 이용했을 때에 비해 DB I/O가 증가한 모습이다. 조금 더 복잡한 모델에서 효율이 나타날 듯 하다.

다음으로는 Repository 내에서 QueryBuilder를 사용하자.

  async findRoom(title: string): Promise<any> {
    return await this.roomRepository
      .createQueryBuilder('room')
      .leftJoinAndSelect('room.user', 'user')
      .leftJoinAndSelect('user.authorities', 'authorities')
      .where('room.title LIKE :title', { title: `%${title}%` })
      .orderBy('room.createdAt', 'DESC')
      .getMany();
  }

쿼리 빌더 만으로 구성했을 때와 크게 달라진 모습은 보이지 않는다.

SELECT ... FROM "rooms" "room" LEFT JOIN "user" "user" ON "user"."id"="room"."user_id"  LEFT JOIN "user_authority" "authorities" ON "authorities"."user_id"="user"."id" WHERE "room"."title" LIKE $1 ORDER BY "room"."created_at" DESC


참고
What are the different use cases for using QueryBuilder vs. Repository in TypeORM?

profile
Action!

0개의 댓글