회사에 백엔드파트 리더로 전 야놀자 리더분께서 오셨다.
오셔서 코드품질과 성능개선에 대해서 설명해주시는데,
그동안 내가 쓰레기같은 코드들만 빨리 짜고 있었구나 싶었다..🥲
그래서 요새는 기존 코드들을 하나씩 리팩토링에 들어가고있다.
TypeOrm을 좀 효율적으로 잘 쓰자고 강조하셨다.
그래서 오늘은 TypeOrm의 loadRelationCountAndMap() 메서드에 대해서 포스팅을 해보려한다.
우선 기존 코드를 먼저 확인해보자.
기존 Code
사진속 코드에 대해서 설명을 하자면 투표 댓글 작성 API 인데,
존재하지 않는 투표거나, 종료된 투표가 내가 참여한 투표가 아니면
댓글을 못달게 에러를 return하는 로직이였다.
vote Repository / voting Repository로 두번 DB Connection이 이뤄지는데
이를 한번만 Connection 하도록 효율적으로 리팩토링을 했다.
이제 loadRelationCountAndMap() 메서드를 사용한 리팩토링한 코드를 보자.
리팩토링한 code
이런식으로 기존과 다르게 한개의 repository 로만 connection이 이뤄진다.
이게 왜 될까?
loadRelationCountAndMap() 를 사용해서 가상의 속성을 매핑할 수 있다.
이렇게 들으면 무슨소리인지 모를테니 좀 더 알아보자.
loadRelationCountAndMap()
특정 엔티티와 연결된 관계의 수를 쉽게 계산하고, 그 결과를 엔티티의 속성에 매핑해주는 기능을 제공한다.
이를 통해 복잡한 쿼리를 작성하지 않고도 관계의 수를 쉽게 얻을 수 있다.
loadRelationCountAndMap()를 사용하려면 각각 엔티티에서 속성이 추가되어야한다.
@OneToMany와 @ManyToOne은 TypeORM에서 제공하는 데코레이터로,
엔티티 간의 1:N(N:1) 관계를 정의해준다.
loadRelationCountAndMap() 은 이러한 관계를 바탕으로 하여,
연관된 엔티티의 개수를 세어 그 결과를 특정 엔티티의 속성으로 매핑하는 기능을 한다.
이제 엔티티 준비가 끝났으니 사용법을 살펴보자.
// VoteEntity 내부에 voted 속성을 추가해야 한다.
@Entity()
export class VoteEntity {
// ... 기타 필드들 ...
// 가상 속성을 추가
voted?: number;
}
// Service 또는 Repository 내부
async function findVoteAndVotingCount(voteId: string, userId: string) {
return await this.repository
.createQueryBuilder("vote")
.loadRelationCountAndMap(
"vote.voted", // 매핑될 속성
"vote.votings", // 관계 경로
"voting", // 쿼리 빌더 내에서 사용될 별칭
(qb) => qb.andWhere("voting.userId = :userId", { userId }) // 조건을 적용하기 위한 콜백 함수
)
.getOne();
}
loadRelationCountAndMap()은 세 가지 주요 인자를 받는다.
1. 매핑될 속성의 이름 - 결과가 매핑될 엔티티 내의 속성 이름.
이 이름은 "entityName.propertyName" 형태로 지정돼야 하며,
entityName은 TypeORM 쿼리 빌더에서 사용된 별칭(alias)이다.
이런식은 voteEntity에 새 속성을 추가시켜준다.
2. 관계(relation)의 경로 - 카운트될 관계의 경로. "entity.relation" 형태로 작성된다.
아까 @OnetoMany에 votings 라고 이름을 정해주었다.
3. 조건을 추가할 수 있는 콜백 함수 (선택적) (QueryBuilder를 인자로 받는 함수)
- 필요한 경우 특정 조건을 적용하기 위해 사용된다.
세 가지 주요 인자에 대해서 알아봤으니 그 다음 qb.andWhere 를 보자.
andWhere 메소드를 사용해서 voting.userId가 파라미터로 받아온 userId와 일치하는 조건을 쿼리에 추가한다.
즉, 특정 사용자가 투표한 것만 세겠다는 의미다.
이제 loadRelationCountAndMap()의 장점을 살펴보자.
- 성능 향상
별도의 카운트 쿼리를 실행하지 않고도 관련 데이터의 수를 가져올 수 있어서
데이터베이스에 대한 요청 횟수가 줄어들고, 결과적으로 애플리케이션의 성능이 향상된다.
- 코드 간결성
관계 수를 셀 때 필요한 추가적인 쿼리 작성 없이 명확한 API를 통해
직관적으로 카운트를 할 수 있어서 코드가 훨씬 간결해진다.
- 유연성
loadRelationCountAndMap는 콜백 함수를 통해 특정 조건에 맞는 카운트만을 셀 수 있도록 해서,
동적인 쿼리 생성이 가능해진다.
다음으로 loadRelationCountAndMap()의 주의할 점을 살펴보자.
- 복잡한 쿼리 성능 저하
만약에 매우 복잡한 조건이나 많은 양의 조인이 필요한 경우, loadRelationCountAndMap 메소드는 성능이 저하될 수 있다.
이럴 땐 성능을 위해 별도의 최적화된 쿼리를 작성하는 것이 좋을 수 있다.
- 인덱싱과 최적화
loadRelationCountAndMap를 사용할 때는 관련 테이블에 적절한 인덱스가 설정되어 있는지 확인해야 한다.
그렇지 않으면 카운트를 계산할 때 성능 문제가 발생할 수 있다.
- 오버페칭 문제
단일 쿼리로 많은 데이터와 그 관계를 가져올 경우, 실제로 필요하지 않은 데이터까지 가져오는 오버페칭 문제가 발생할 수 있다.
따라서 어떤 데이터가 실제로 필요한지 잘 파악하고 쿼리를 작성해야 한다.
- 엔티티 설계
loadRelationCountAndMap를 사용하기 위해서는 엔티티 간의 관계가 올바르게 매핑되어 있어야 한다.
때때로 이런 관계 설정이 복잡할 수 있는데, 이때 잘못 설계되면 예상치 못한 결과가 나올 수 있다.
이렇게 loadRelationCountAndMap() 메서드에 대해서 자세히 알아보았다.
앞으로도 엔티티 관계가 잡혀있고 여러 Connection을 해야할 경우엔
loadRelationCountAndMap() 를 사용하여 효율성 높은 코드를 짜야겠다 !
'Web > TypeOrm' 카테고리의 다른 글
[TypeORM] 밤 12시, 수만 명의 출석체크에 대응한 동시성 문제 해결기 (1) | 2024.03.20 |
---|---|
[TypeOrm] TypeORM으로 upsert(), save() 안될 시 해결방법 (2) | 2023.03.18 |
[TypeOrm] IN조건으로 배열 검색 "Typeorm find where in array" (0) | 2023.02.15 |
[TypeOrm] timezone: 'z' charset UTC시간 설정 (0) | 2023.01.28 |
[TypeOrm] 차이를 알고쓰자 save() , insert() , update() (0) | 2023.01.25 |