본문 바로가기
Projects/NPE

[TypeORM] 쿼리빌더를 통해 데이터 가져오기

by DawIT 2021. 11. 7.
320x100

현재상황과 문제

먼저, 진행하고 있는 프로젝트에서 graphql 쿼리를 작성하기 위해 하나의 질문글에 달린 태그를 가져와야 한다.

 

먼저 필요한 테이블들의 관계는 다음과 같다.

 

 

post_question과 tag가 N:M 관계를 가지고 있고, 연결 테이블로 question_tags 를 가지고 있다.

 

graphql 질문글 검색 쿼리는 다음과 같은 상황이다.

 

 

post_questions 요청을 통해 검색할 태그 ID 배열을 넘겨주고, 질문글의 제목과, 할당되어있는 태그들을 가져오는 쿼리를 작성한 모습이다. 요구사항은 간단한데 처음에 생각보다 구현에 급급하여 코드를 날림으로 작성해놓았다.

 

 

먼저 다른 조건들(whereObj)을 통해 해당하는 게시글들을 불러오고, tagID 가 검색 쿼리에 포함되어있다면 불러온 질문글들을 for문 을 돌려서 join을 하고 post_question_has_tags 테이블과 조인을 한 뒤, tagId 만 뽑아서 Array.every함수를 통해 일일이 돌려서 확인한다. 지금 보니까 tagID 라고 쓰기도 하고 tagId로 쓰기도 하고 아주 난장판이다. 기능 구현에 급급하여 이런 가독성 떨어지는 코드를 작성하게 된 것 같다.

필요한 SQL쿼리 작성해보기

TypeORM에 지금은 익숙치 않아서, 먼저 mysql cli 에서 필요한 쿼리를 직접 작성해보기로 했다.

 

 

 

현재 테이블에 들어가있는 데이터는 이런 상황이다. 먼저 여기에서 1번과 2번 태그를 둘 다 포함하고 있는 질문글들의 ID를 가져오는 상황을 가정하고 쿼리를 작성하고, 이를 서브쿼리로 하여 질문글을 조회할 것이다.

 

INNER JOIN을 태그 개수만큼 사용할 수도 있었는데(실제로 처음 개선했을때 그렇게 작성했었다), 너무 쿼리가 커지고 성능상으로도 JOIN을 계속 하는것보다 그냥 가져오는게 좋을 것 같아서 이런 쿼리를 작성하게 되었다.

 

SELECT postQuestionId qid
FROM question_tags q_t
WHERE tagId IN (1,2)
GROUP BY qid
HAVING COUNT(qid) = 2;

 

tagId 1 과 2 둘중 하나라도 포함하는 ROW들을 모두 가져온 뒤에 question ID로 GROUP BY를 수행하여 묶은 뒤 ROW가 2개인 qid만 HAVING으로 걸러주면 된다. 여기서는 1과 2밖에 없어서 2개지만 검색하는 태그 갯수만큼 걸러주어야 할 것이다.

 

 

실행시키면 이렇게 1번과 2번 태그를 모두 가지고 있는 qid만 넘어온다. 이를 바탕으로 질문글을 검색하기 위해서 WHERE.조건의 IN 안에다 저 쿼리를 서브쿼리로 사용하면 된다.

 

 

SELECT id, title, createdAt FROM post_question
WHERE id IN
(
SELECT postQuestionId qid
FROM question_tags q_t
WHERE tagId IN (1,2)
GROUP BY qid
HAVING COUNT(qid) = 2
)

 

 

지금은 태그 ID로 걸러내는 조건밖에 없지만 다른 조건(제목, 내용)이 필요하다면 WHERE 절에 추가만 해주면 된다.

 

TypeORM 으로 옮기기

이제 작성한 쿼리를 TypeORM에서 쓰기 위해 QueryBuilder를 사용할 것이다.

 

QueryBuilder is one of the most powerful features of TypeORM - it allows you to build SQL queries using elegant and convenient syntax, execute them and get automatically transformed entities.

 

쿼리빌더는 TypeORM에서 쿼리를 우아하고 편리한 문법으로 작성하게 해주는 기능이라고 한다.

 

나는 QuestionRepository (TypeORM의 CustormRepository) 내에서 쿼리빌더를 만들고 쿼리를 수행했다.

 

 

작성된 쿼리빌더의 코드를 보면 상당히 직관적이고 편리하다는 것을 알 수 있다. 거의 설명이 필요 없을 정도로 직관적인데, 하나 주의할 점은 칼럼을 선택할 때 alias를 꼭 정해주어야 한다.

 

또한 주의해야 할 점은 Entity 를 가져올 때에는 getMany 혹은 getOne 을 사용하면 되는데, raw Data를 가져오려면 getRawMany를 사용해야 한다.

 

해당 함수가 실행되면 [ { qid: 1 }, { qid: 2 }, { qid: 8 } ]  의 형태로 결과가 넘어오게 된다. 이를 적절히 매핑하여 FindConditions 객체에 넣어주면 된다. 내가 작성한 함수 전체 코드는 다음과 같다.

 

 

사실 whereInIds 등을 통해서 하나의 거대한 queryBuilder로도 수행할 수 있었지만 실제로 작성해보니 너무 길어져서 가독성이 상당히 떨어지는것 같아서 쿼리를 2번 날리는 방향으로 전환했다.

 

어쨌든 이렇게 필요한 조건들을 가져와 적절히 파싱하고 마지막에 find 메서드에 조건을 집어넣으면 알맞게 가져와진다.

 

드는 생각

사실 엄청 어려울 줄 알고 각잡고 글을 쓰려고 했는데 막상 공부하고 코드를 작성해보니 훨씬 편하고 직관적이어서 좀 의외였다. 다음부터는 쿼리를 먼저 작성할 필요 없이 바로 쿼리빌더를 통해 코드를 작성할 수 있을 것 같다.

댓글