Nest : 쿼리빌더 형식 정립?

Gwonyeong·2023년 6월 1일
0

유키독

목록 보기
8/11
@Injectable()
export class SetQuery {
  protected query: SelectQueryBuilder<Keyword>;

  @InjectRepository(Keyword)
  protected keywordRepository?: Repository<Keyword>;
  @InjectRepository(KeywordUser)
  protected keywordUserRepository?: Repository<KeywordUser>;

  // 키워드가 유저에게 존재하는지 확인
  protected setIsExistKeyword() {
    this.query.leftJoin(`KeywordUser`, `KU01`, `K01.id = KU01.keywordId`);

    this.query.addSelect(
      `CASE WHEN KU01.keywordId IS NULL THEN false ELSE true END AS isExistKeyword`,
    );
  }

  // 키워드 검색
  protected setSearchByKeyword(keyword: string) {
    this.query = this.query.where(`K01.keyword LIKE :keyword`, {
      keyword: `%${keyword}%`,
    });
  }

  // 유저 아이디로 키워드 검색
  protected setWhereByUserId(userId: number) {
    this.query = this.query
      .innerJoin(`KeywordUser`, `KU01`, `K01.id = KU01.keywordId`)
      .innerJoin(`user`, `U01`, `KU01.userId = U01.id`)
      .where(`U01.id = :userId`, { userId });
  }

  // limit 설정
  protected setLimit(page?: number, limit?: number) {
    page = page ?? 1;
    limit = limit ?? 10;

    const offset = (page - 1) * limit;
    this.query = this.query.limit(limit).offset(offset);
  }
}

export class FindAllKeywordQueryBuilder extends SetQuery {
  constructor() {
    super();
  }
  // 모든 키워드를 찾기
  public async findAll(findAllKeywordDto: FindAllKeywordDto) {
    this.query = this.keywordRepository
      .createQueryBuilder(`K01`)
      .select([
        `K01.id AS keywordId`,
        `K01.keyword AS keyword`,
        `K01.createdAt AS createdAt`,
        `K01.count AS count`,
      ])
      .groupBy(`K01.id`);

    this.setSearchByKeyword(findAllKeywordDto.keyword);
    this.setLimit(findAllKeywordDto.page, findAllKeywordDto.limit);
    this.setIsExistKeyword();

    return await this.query.getRawMany();
  }
}

export class FindAllByUserIdQueryBuilder extends SetQuery {
  constructor() {
    super();
  }

  // 해당 유저가 가진 키워드만 조회
  public async findAllByUserId(findKeywordByUserIdDto: FindKeywordByUserIdDto) {
    this.query = this.keywordRepository
      .createQueryBuilder(`K01`)
      .select([`K01.*`]);

    this.setWhereByUserId(findKeywordByUserIdDto.userId);
    this.setLimit(findKeywordByUserIdDto.page, findKeywordByUserIdDto.limit);
    this.setIsExistKeyword();

    return await this.query.getRawMany();
  }
}

export class FindAllRecomendedKeywordQueryBuilder extends SetQuery {
  constructor() {
    super();
  }
  // 추천 키워드 찾기
  public async findAllRecomendedKeyword(
    findRecommentKeywordDto: FindRecommentKeywordDto,
  ) {
    this.query = this.keywordRepository
      .createQueryBuilder(`K01`)
      .select([
        `K01.id AS keywordId`,
        `K01.keyword AS keyword`,
        `K01.createdAt AS createdAt`,
        `K01.count AS count`,
      ])
      .groupBy(`K01.id`);

    switch (findRecommentKeywordDto.recomendType) {
      case 'recent':
        this.query.orderBy(`K01.id`, `DESC`);
        this.setLimit(1, findRecommentKeywordDto.limit);
        break;
      case 'popular':
        this.query.orderBy(`K01.count`, `DESC`);
        this.setLimit(1, findRecommentKeywordDto.limit);
        break;
      case 'recommend':
        this.query.orderBy(`K01.count`, `DESC`);
        this.setLimit(1, findRecommentKeywordDto.limit);
        break;
    }
    return await this.query.getRawMany();
  }
}

쿼리를 작성하는건 항상 생각하게된다.
아무랩스의 회사에서 배운점은 SQL 쿼리를 작성할 때 정말 필요한 JOIN, WHERE등의 조건을 걸어야 한다는 것이다.

예를 들어 title에서 특정 키워드로 검색을 한다면 매개변수가 ""로 들어왔을 때,
WHERE title LIKE '%%'; 이런 WHERE문을 붙이는게 아니라 아예 WHERE문이 붙지 않도록 만들어야 한다.

안그래도 SQL쿼리는 느린데 필요하지 않은 조건이 붙는다면 데이터를 가져오는 것이 느린데에 큰 지분을 차지하게 될 것이다.

@Injectable()
export class SetQuery {
protected query: SelectQueryBuilder<Keyword>;

  @InjectRepository(Keyword)
  protected keywordRepository?: Repository<Keyword>;
  @InjectRepository(KeywordUser)
  protected keywordUserRepository?: Repository<KeywordUser>;

  // 키워드가 유저에게 존재하는지 확인
  protected setIsExistKeyword() {
    this.query.leftJoin(`KeywordUser`, `KU01`, `K01.id = KU01.keywordId`);

    this.query.addSelect(
      `CASE WHEN KU01.keywordId IS NULL THEN false ELSE true END AS isExistKeyword`,
    );
  }

  // 키워드 검색
  protected setSearchByKeyword(keyword: string) {
    this.query = this.query.where(`K01.keyword LIKE :keyword`, {
      keyword: `%${keyword}%`,
    });
  }

  // 유저 아이디로 키워드 검색
  protected setWhereByUserId(userId: number) {
    this.query = this.query
      .innerJoin(`KeywordUser`, `KU01`, `K01.id = KU01.keywordId`)
      .innerJoin(`user`, `U01`, `KU01.userId = U01.id`)
      .where(`U01.id = :userId`, { userId });
  }

  // limit 설정
  protected setLimit(page?: number, limit?: number) {
    page = page ?? 1;
    limit = limit ?? 10;

    const offset = (page - 1) * limit;
    this.query = this.query.limit(limit).offset(offset);
  }
}

나는 이 부모쿼리를 작성해서 다른 클래스들이 상속받도록 만들었다.
여러가지 조건을 이용해 필요한 키워드만 가져올 수 있어야 하기때문에 키워드 검색, 특정 유저가 가진 키워드 검색 등
JOIN이나 WHERE를 중점적으로 부모에게 작성했다.
자식 클래스들은 이 부모 클래스를 상속받아 필요한 WHERE, JOIN을 쿼리에 붙이도록 만든다.

조금 더 생각해보면 setLimit같은 함수는 아마 많은 SELECT쿼리에서 사용하게 될 것이기 때문에 SetQuery의 부모 클래스를 하나 더 만들어 공용의 메소드로 넣어줘도 좋을 것 같다.

분명 이 방식이 최고의 방식은 아닐 것 같다. 하지만 지금 작성한 현재의 나의 시점에서는 나쁘지 않은 방법으로 여러가지 쿼리를 작성할 수 있는 빌더를 만들었다고 생각한다.

어떻게하면 더 좋은 코드를 만들고 더 쉽게 알아볼 수 있는 코드를 만들 수 있을까?
주기적으로 코드를 볼 때마다 고민을 하는 내 모습이 나쁘지는 않은 것 같다.

profile
부동의 첫사랑

0개의 댓글

관련 채용 정보