JPA

[JPA] Querydsl DTO로 조회하기

da77777 2022. 7. 25. 06:12

group by, order by 등을 써야해서 querydsl을 사용했다.

 

일단 아래의 코드를 통해 원하는 값을 조회하는 것은 성공

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class TopContentsDTO {

    private int likes;
    private Contents contents;
}
    @Test
    void selectBestContents() {
        QAssessment qA = QAssessment.assessment;

        List<Tuple> result = jpaQF
                .select(qA.likes.count(), qA.contents.id, qA.contents.contentsName)
                .from(qA)
                .where(qA.likes.eq(1))
                .groupBy(qA.contents.id)
                .orderBy(qA.likes.count().desc())
                .limit(3)
                .fetch();

        for(int i = 0; i < result.size(); i++) {
            System.out.println("----- " + result.get(i));
        }
    }

 

그런데

여기서 각각의 값을 어떻게 가져올지를 모르겠어서 방법을 찾아보니 딱히 끌리지 않았다.

'이런 방법을 사람들이 그대로 사용할리가 없다 분명 더 좋은 방법이 있을 것이다' 라는 확신...

그리고 List<>안에 타입을 DTO로 하고싶기도 했다.

 

 


 

 

그래서 찾아본 방법

  1. Projections (bean, fields, constructor)
  2. @QueryProjection

 

Projections

컴파일 시에는 에러를 잡을 수 없다. 런타임 시에만 에러를 잡을 수 있다. 

 

Projections.bean / Projections.fields

.bean은 setter를 기반으로 동작한다.

.fields는 필드에 직접 값을 넣으므로 setter는 필요 없다.

bean과 fields은 필드 이름이 다를 경우 값을 가져오지 못한다.

해결책으로는 .as("DTO 변수 이름"); 을 사용할 수 있다.

 

Projections.constructor

생성자 기반으로 바인딩해준다. 

→ DTO 객체를 불변으로 가질 수 있다. 

 필드 이름이 달라도 상관 없다.

 조회 값과 DTO 변수 타입이 일치해야 한다. 

값을 넘길 때 생성자와 순서를 일치시켜줘야하므로, 값이 많아질 경우 문제가 생길 수 있다.

    @Test
    void selectBestContents() {
        QAssessment qA = QAssessment.assessment;

        List<TopContentsDTO> result = jpaQF
                .select(Projections.constructor(TopContentsDTO.class, qA.likes.count().intValue(), qA.contents))
                .from(qA)
                .where(qA.likes.eq(1))
                .groupBy(qA.contents.id)
                .orderBy(qA.likes.count().desc())
                .limit(3)
                .fetch();
    }

 

 

@QueryProjection

불변 객체 선언 및 생성자를 유지할 수 있다.

컴파일 시 에러를 잡을 수 있다. 

DTO 특성 상 많은 계층에서 사용하게 되는데, qureydsl의 의존성이 필요 없는 경우에도 해당 의존성이 필요하게 된다. 

 

@Getter
@NoArgsConstructor
public class TopContentsDTO {

    private int likes;
    private Contents contents;

    @QueryProjection
    public TopContentsDTO(int likes, Contents contents) {
        this.likes = likes;
        this.contents = contents;
    }
}
    @Test
    void selectBestContents() {
        QAssessment qA = QAssessment.assessment;

        List<TopContentsDTO> result = jpaQF
                .select(new QTopContentsDTO(qA.likes.count().intValue().as("likes"), qA.contents))
                .from(qA)
                .where(qA.likes.eq(1))
                .groupBy(qA.contents.id)
                .orderBy(qA.likes.count().desc())
                .limit(3)
                .fetch();

        for(int i = 0; i < result.size(); i++) {
            int likes = result.get(i).getLikes();
            Contents contents = result.get(i).getContents();
            Long contentsId = contents.getId();
            String contentsName = contents.getContentsName();

            System.out.println("-----" + likes + " " +contentsId + " " + contentsName); //
        }
    }

 

 


 

 

Projections.constructor 과 @QueryProjection 중에 고민했는데

 @QueryProjection을 사용해보고자 한다.

필요없는 의존성이 생긴다는 부분이 걸려서 괜찮은가 싶지만

개인적으로는 아래와 같이 컴파일 시 에러를 잡을 수 있다는 점이 매력적이라서;

 

Projections.constructor 사용 시에는 qA.liks.count() 까지만 해줘도 에러표시가 안 났는데

@QueryProjection 방식으로 하니 바로 이렇게..!

 

참고

qA.likes.count().intValue()

  • qA.likes.count() : sql로 보자면 count(likes) 
  • .intValue() : count()가 Long 타입을 반환하므로 int로 처리해줌
    intValue()를 해주기 전에는 java.lang.long is not compatible with int 이라는 에러가 발생했다.

 

 

 

 

 

 


참고

https://doing7.tistory.com/129

 

[Querydsl] 튜플이나 DTO로 결과 반환하기

프로젝션 : select 대상지정하는 일 프로젝션 대상이 두개 이상이라면 튜플이나 DTO로 조회해야한다. 🌱 튜플 사용하기 com.querydsl.core.Tuple를 사용하고 있다. 때문에 Repository 계층을 넘어서 Service나

doing7.tistory.com

https://wildeveloperetrain.tistory.com/94

 

Querydsl DTO 조회하는 방법(Projection, @QueryProjection)

Projection 연산이란, - 한 Relation의 Attribute들의 부분 집합을 구성하는 연산자입니다. - 결과로 생성되는 Relation은 스키마에 명시된 Attribute들만 가집니다. - 결과 Relation은 기본 키가 아닌 Attribute..

wildeveloperetrain.tistory.com