본문 바로가기

백엔드/QueryDSL

QueryDSL 프로젝션, DTO 사용

프로젝션

특정 필드만 조회할 경우 해당 필드의 타입 리스트가 반환값이 된다. 만약 조회하는 필드가 두개 이상이라면

이렇게 튜플이 된다. 튜플은 쿼리dsl에서 제공해주는 것이다. 그러므로 레포지토리 계층에서만 사용할 수 있도록 해야한다.

 

DTO를 사용한 프로젝션

튜플을 레포지토리 계층에서만 사용하기 위해서는 서비스 계층으로 값을 반환할때 DTO를 사용해야 한다.

JPA에서 공부했던 JPQL 쿼리를 DTO로 조회할 때를 잠깐 복습해보면 

@Test
public void projectionWithDTO() {
  List<MemberDTO> resultList = em.createQuery("select new study.QueryDSL.dto.MemberDTO(m.username, m.age) from Member m", MemberDTO.class).getResultList();
  for (MemberDTO memberDTO : resultList) {
  	System.out.println("memberDTO.getName() = " + memberDTO.getName());
  }
}

select 절에다가 new DTO클래스() 하면서 가져오는 것이다. 이렇게 하기 위해서는 해당 인자들을 받는 생성자를 만들어둬야한다. 즉 JPA에서의 DTO조회는 생성자 방식만 지원해준다. 일단 불편한 점은 new 경로() 를 입력하는 것이 매우 번거롭다는 점이다.

 

🎇쿼리 DSL에서의 DTO로 조회🎇

1. 프로퍼티 접근 방식

    @Test
    public void projectionWithDTO() {
        List<MemberDTO> fetch = qf.select(Projections.bean(MemberDTO.class, member.username, member.age))
                .from(member)
                .fetch();
        for (MemberDTO memberDTO : fetch) {
            System.out.println("memberDTO = " + memberDTO.getName());
        }
    }

JPA 의 예시에서와 똑같은 조회쿼리인데 훨씬 편하다. 이 방법은 실행과정이 기본생성자로 DTO를 생성하고 setter를 통해서 값을 세팅해준다. 그러므로 setter가 없으면 출력 결과는 null, 기본 생성자가 없으면 에러가 발생한다.

 

2. 필드 직접 접근 방식

    @Test
    public void projectionWithDTO() {
        List<MemberDTO> fetch = qf.select(Projections.fields(MemberDTO.class, member.username, member.age))
                .from(member)
                .fetch();
        for (MemberDTO memberDTO : fetch) {
            System.out.println("memberDTO = " + memberDTO.getName());
        }
    }

이 방법은 setter가 없어도 괜찮다.

 

3. 생성자 방식

    @Test
    public void projectionWithDTO() {
        List<MemberDTO> fetch = qf.select(Projections.constructor(MemberDTO.class, member.username, member.age))
                .from(member)
                .fetch();
        for (MemberDTO memberDTO : fetch) {
            System.out.println("memberDTO = " + memberDTO.getName());
        }
    }

생성자가 받는 인자의 순서를 맞춰줘야 한다. 만약 DTO의 필드 이름이 엔티티의 필드 이름과 맞지 않다면

List<MemberDTO> fetch = qf
		.select(Projections.constructor(MemberDTO.class, member.username.as("name"), member.age))
                .from(member)
                .fetch();

as() 함수를 사용해서 맞춰주면 된다.

 

@QueryProjection의 장단점

프로젝션 결과를 조회하는 방법이다. 사용 방법은 간단하다.

@Getter
@NoArgsConstructor
public class MemberDTO {
    private String name;
    private int age;

    @QueryProjection
    public MemberDTO(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

1. 생성자 위에다가 @QueryProjection을 붙이기 (@AllArgsConstructor 를 못쓰는건 좀 슬프다.. 벌써 롬복의 노예가 되어버린...) 

2. compileQuerydsl 실행

    @Test
    public void QueryProjection() {
        qf.select(new QMemberDTO(member.username, member.age))
                .from(member)
                .fetch();
    }
    
    
    //생성자 방식
    List<MemberDTO> fetch = qf.select(Projections.constructor(MemberDTO.class, member.username.as("name"), member.age))
                .from(member)
                .fetch();

생성자 방식과 전혀 다를 바 없어보이지만 인자의 타입과 갯수를 제대로 맞춰주지 않으면 컴파일 에러를 내준다는 장점이 있다. 하지만 단점은 Q타입 클래스를 만들어야 한다는 점, DTO가 쿼리 dsl에 의존하게 된다는 것. 이는 여러 계층을 돌아다녀야 하는 DTO의 특성상 좋지가 않다.

 

 

관련글

https://www.inflearn.com/course/Querydsl-%EC%8B%A4%EC%A0%84/dashboard

 

실전! Querydsl - 인프런 | 강의

Querydsl의 기초부터 실무 활용까지 한번에 해결, 본 강의는 자바 백엔드 개발의 실전 코스를 완성하는 마지막 강의 입니다. 스프링 부트와 JPA 실무 완전 정복 로드맵을 우선 확인해주세요. 로드

www.inflearn.com

 

 

 

'백엔드 > QueryDSL' 카테고리의 다른 글

QueryDSL - 동적 쿼리  (0) 2021.09.01
쿼리 dsl 기초  (0) 2021.08.30
쿼리 dsl 입문  (0) 2021.08.30