토이 프로젝트 배우게 된 것들 & 오류 해결🐰

JPA | QueryDsl 집계함수[SUM, COUNT ..] 사용 시, Dto로 반환

j_estory 2023. 1. 4. 21:38

✅ 고민되었던 부분

 

querydsl 에서 집계함수를 사용하게 되면 반환값이 Tuple 형식으로 반환되게 된다.

Tuple 형식을 사용해보려고 했지만 반환되는 값에서 필드 하나하나를 가져와 다시 dto에 맵핑해야 하는 상황이 발생되었고,

Tuple 자체가 querydsl 객체이기 때문에 Repository 계층이 아닌, 
Service 계층까지 가져오는 것을 막고 싶었다.

 

위의 방법을 찾아보다가 집계함수 사용 시, groupby와 projection을 사용하여 dto로 받아올 수 있다는 사실을 알게 되었고,

projection에 대하여 공부해보고, 사용 방법에 대해서도 적어보려고 한다!

 

 

✅ Projection 연산자란 ..?

 

  • 한 Relation의 Attribute들의 부분 집합을 구성하는 연산자이다.
  • 결과로 생성되는 Relation은 스키마에 명시된 Attribute 들만 가지게 된다.
더보기

Relation: 데이터를 원자 값으로 갖는 이차원 테이블

Attribute: 속성 값 (=column)

간단하게 말하면 projection은 "테이블에서 원하는 컬럼만 뽑아서 조회하는 것" 을 의미한다.

 

 

QueryDsl 에서 DTO로 조회 하는 방법

  1.  projections.bean
  2.  projections.fields
  3. projections.constructor
  4. @QueryProjection

위와 같이 총 4가지가 존재하며, 저의 프로젝트에서 어떠한 방법을 쓰면 좋을지 공부해보고 결정해보기로 했다.

 

 

1️⃣ projections.bean

 

setter 메서드를 기반으로 동작한다. 

때문에 dto 객체의 각 필드에 setter 메소드가 존재해야 한다. 

하지만 해당 방법은 아래와 같은 이유로 권장하지 않는다고 한다

- 단순히 조회용으로만 사용될 dto라면, 불필요한 변경을 주는 것은 피해야 한다.

- Request, Response 객체의 경우에는 불변의 객체를 만드는 것이 바람직하다.

- 만약 영속화된 데이터를 반영하는 책임이 가진 객체라면 문제가 될 수 있다.

 

2️⃣ projections.fields

 

getter, setter 메소드 없이 field에 값을 직접 주입해주는 방식이다.

위의 projections.bean 방식과 마찬가지로 Type이 다른 경우 매칭이 되지 않으며,

컴파일 시점에 에러를 잡지 못하고, 런타임 시점에서 에러가 잡히게 된다.

 

만약, 필드명 매칭이 제대로 되지 않는 경우라면 alias를 사용하여 매칭해주면 된다.

 

3️⃣ projections.constructor

 

생성자를 기반으로 바인딩 해주는 방식이다.

생성자 기반 바인딩이기 때문에 객체가 불변성을 가져갈 수 있다는 장점이 있다. 

해당 방법은 가지고 온 값을 생성자에서 한번 더 가공할 수 있다는 장점도 있다. 

 

위 방법은 Dto 객체의 생성자에게 직접 바인딩하는 것이 아니라

Expression<?> ... exprs 값을 넘기는 방식으로 작동하는데,

값을 넘길 때 생성자와 필드의 순서까지 일치시켜야 한다.

필드의 개수가 적을때는 문제가 되지 않지만, 필드의 개수가 많아지는 경우 실수가 발생하여 오류로 이어질 수 있다.

 

하지만 !!

생성자를 이용한 방식은 필드명이 달라도 정상적으로 동작한며 

생성자 방식도 파라미터를 잘못 작성할 경우, 컴파일 시점이 아닌 런타임 시점에 오류를 발생한다.

 

4️⃣  @QueryProjection

 

위 방식은 불변 객체 선언, 생성자를 그대로 사용할 수 있기에 권장되는 패턴이다. 

정확히는 Dto의 생성자를 사용하는 것이 아니라,

Dto 기반으로 생성된 QDto 객체의 생성자를 사용하는 것이다.

 

작동 방식은 Dto 생성자의 어노테이션을 추가하여 QType의 클래스를 생성한다.

이 방식은 new QDto로 사용하기 때문에 런타임 에러뿐 아니라 컴파일 시점에서도 에러를 잡아주고,

파라미터로도 확인할 수 있다는 장점도 있다

 

위 어노테이션을 사용하게 된다면 Dto는 Repository 계층의 조회 용도뿐만 아니라

service, controller 모두 사용되기 때문에, 모든 계층에서 쓰이는 Dto가 querydsl의 의존성을 가지기 때문에 

아키텍처 적으로 적용을 생각해 볼 필요가 있다.

 

return jpaQueryFactory
.select(
        new QReviewDto_ReviewSum(QReview.review.room.roomSeq
        , QReview.review.cleanlinessStarScore.sum()
        , QReview.review.convenienceStarScore.sum()
        , QReview.review.kindnessStarScore.sum()
        , QReview.review.locationStarScore.sum()
        , QReview.review.room.roomSeq.count())
).from(QReview.review)
.groupBy(QReview.review.room.roomSeq)
.fetch();

 

🤔 어떤 방식을 사용하면 좋을까?

 

의존성을 가진다는게 많이 크리티컬 할까..? 

projection을 좀 더 안전하게 사용하는게 좋지 않을까 ..?