QueryDsl - 프로젝션의 결과반환

김건우·2022년 12월 13일
2

QueryDsl

목록 보기
3/8
post-thumbnail

프로젝션이란 ?

QueryDsl을 이요하여 entity 전체의 값을 가져오는 것이 아닌 조회 대상을 지정해 원하는 값만 조회하는 것을 의미한다. 쉽게 말하자면 Projection이란, '테이블에서 원하는 컬럼만 뽑아서 조회하는 것 이다.

먼저 프로젝션의 대상이 하나이면 명확하게 타입을 지정할 수 있고 반환되는 타입도 명확하다.(ex: member에 대한 select를 한다면 member타입이 반환된다.)
하지만 문제는 프로젝션 대상이 둘 이상이면 튜플이나 DTO로 조회해야한다

튜플 조회 - 프로젝션 대상이 2개 이상일 경우

  • 프로젝션이 2개 이상일 때 반환 타입이 엔티티의 명확한 타입이 아닌 튜플 타입으로 조회된다
    @Test
    @DisplayName("튜플")
    void getTuple () throws Exception {

        List<Tuple> result = jpaQueryFactory
                .select(member.username, member.age)
                .from(member)
                .fetch();

        for (Tuple tuple : result) {
            String userName = tuple.get(member.username);
            Integer userAge = tuple.get(member.age);
            System.out.println("userName = " + userName);
            System.out.println("userAge = " + userAge);
        }
    }

중요한 팁 : 튜플로 값을 가져오는 경우는 Querydsl에 종속적이고, Model 객체를 로직에서 사용하는 것과 같은 문제를 가지고 있기에 최대한 사용을 피하고 Repository에서만 사용하는 것이 좋다. 만약 Repository 바깥으로 나갈 땐 DTO로 변환하는 것을 권장한다. 다시 풀어서 말하자면 Repository에서 QueryDsl로 조회 후 튜플로 반환됐다면 repository에서 튜플을 -> dto로 바꿔주는 것을 권장한다.

DTO 조회의 3가지 방법

우선 조회 응답에 필요한 MemberResponse를 제작해주겠다.

  • MemberResponse DTO Class
package com.spring.jpadata.dto;

import com.querydsl.core.annotations.QueryProjection;
import lombok.Data;

@Data
public class MemberResponse {

    private String username;
    private int age;

    public MemberResponse() {
    }
    
    public MemberResponse(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

이제부터 3가지 방법을 소개할텐데, 셋은 공통점이 있다
Projections.XXX(반환타입,조회할 필드1,필드2,....) 의 형식을 갖을 것이다.🔽

1.프로퍼티 접근 방법 - setter

Projections.bean

setter(bean)를 통해 데이터를 인젝션 해주며 기본 생성자가 무조건 필요하다.

    @Test
    @DisplayName("쿼리디에스엘 dto setter접근")
    void dto() throws Exception {
        //dto로 조회
        List<MemberResponse> result = jpaQueryFactory.select(
                Projections.bean(MemberResponse.class,
                        member.username,
                        member.age)
        ).from(member)
                .fetch();

        for (MemberResponse memberResponse : result) {
            System.out.println("memberResponse = " + memberResponse);
        }

    }

2. 필드 접근 방법 - filed

Projections.fields

필드에 직접적으로 값을 꽂아주기 때문에 setter와 기본생성자는 필요없다.

    @Test
    @DisplayName("쿼리디에스엘 dto 필드접근")
    void dtoByField() throws Exception {
        //dto로 조회
        List<MemberResponse> result = jpaQueryFactory.select(
                Projections.fields(MemberResponse.class,
                        member.username,
                        member.age)
        ).from(member)
                .fetch();

        for (MemberResponse memberResponse : result) {
            System.out.println("memberResponse = " + memberResponse);
        }

    }

3. 생성자 사용 접근 방법 - contructor

Projections.constructor

값을 넘길 때 생성자와 순서가 맞아야 컴파일 에러가 안나며 , @AllArgsConstructor 가 필요하다.

    @Test
    @DisplayName("쿼리디에스엘 dto 생성자접근")
    void dtoByConstructer() throws Exception {
        //dto로 조회
        List<MemberResponse> result = jpaQueryFactory.select(
                Projections.constructor(MemberResponse.class,
                        member.username,
                        member.age)
        ).from(member)
                .fetch();

        for (MemberResponse memberResponse : result) {
            System.out.println("memberResponse = " + memberResponse);
        }
    }

@QueryProjection

  • 우선 @QueryProjection을 사용하는 방법을 알아보자 아까 만든 MemberResponse DTO의 생성자에 @QueryProjection 를 붙여주자!
@Data
public class MemberResponse {
    private String username;
    private int age;

    public MemberResponse() {
    }

    @QueryProjection // <- 생성자에 @QueryProjection를 붙여주자
    public MemberResponse(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

@QueryProjection을 이용하면 불변 객체 선언, 생성자를 그대로 사용할 수 있기 때문에 권장되는 패턴이다. 정확하게는 DTO의 생성자를 사용하는 것이 아니라 DTO 기반으로 생성된 QDTO 객체의 생성자를 사용하는 것이다. 이제 다음에는 QType class를 생성해 볼것이다.

compileQuerydsl을 빌드하여 QMemberResponse객체를 만들어주자!

  • Gradle의 compileQuerydsl을 빌드해주자!🔽
  • DTO도 @QueryProjection를 붙여주었더니 QType class로 생성이 되었다.🔽

@QueryProjection 어노테이션을 달아주고, compileQuerydsl을 실행해주면 DTO도 Q파일로 생성을 해준다. Contructor는 컴파일 오류를 잡지 못하고 런타임 오류가 일어나기에 유저가 코드를 실행하는 순간 문제를 발견할 수 있지만, QueryProjection은 컴파일러로 타입을 체크할 수 있으므로 가장 안전한 방법이다. 하지만 DTO에 QueryDSL 어노테이션을 유지해야 하기에 QueryDSL에 종속적인 점과 DTO까지 Q파일을 생성해야 하는 단점이 있다.

@QueryProjection test code

    @Test
    @DisplayName("@QueryProjection")
    void dtoByAnnotaion() throws Exception {

        //컴파일시 에러를 잡아줄 수 있다.
        List<MemberResponse> memberResponseList = jpaQueryFactory
                .select(new QMemberResponse(member.username, member.age))
                .from(member)
                .fetch();

        for (MemberResponse memberResponse : memberResponseList) {
            System.out.println("memberResponse = " + memberResponse);
        }
    }
  • 위의 테스트 코드를 실행시 나온 결과 🔽
select
        member0_.username as col_0_0_,
        member0_.age as col_1_0_ 
    from
        member member0_
        
출력🔽
memberResponse = MemberResponse(username=member1, age=10)
memberResponse = MemberResponse(username=member2, age=20)
memberResponse = MemberResponse(username=member3, age=30)
memberResponse = MemberResponse(username=member4, age=40)
profile
Live the moment for the moment.

2개의 댓글

comment-user-thumbnail
2022년 12월 13일

와 감사해요 어려운 부분이였는데 많이 배워갑니다 ^^

1개의 답글