QueryDSL 사용법, DTO 반환 방법 정리

스프링부트 3.x 버전에서 QueryDSL 설정하기

 

[QueryDSL] 스프링부트 3.x 버전에서 QueryDSL 설정하기

스프링 부트 2.x 버전 대를 쓰다가 이번에 스프링 부트 3.x 버전이 나오면서 많은 설정이 변경되었다. 이번 포스팅에서는 스프링부트 3.x 버전에서 QueryDSL을 설정하는 방법에 알아보고자 한다. (스

hstory0208.tistory.com


설명하기 앞서, QueryDSL을 사용하기 위해서는 JPAQueryFactory를 Bean 등록하고 의존성 주입받아야 한다.

 

Bean 등록
@Configuration
public class QueryDSLConfig {

    @Bean
    JPAQueryFactory jpaQueryFactory(EntityManager em) {
        return new JPAQueryFactory(em);
    }
}

 

이번 포스팅에서 사용할 Repository

이번 포스팅에서 설명하기 위해 사용할 리포지토리는 다음과 같다.

아래에서 작성한 querydsl 코드는 이 repository 안에서 작성된 코드라고 생각하면 된다.

@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom {
    private final JPAQueryFactory queryFactory;
    
    ...
}

이 리포지토리는 QueryDSL을 사용하기 위한 사용자 정의 리포지토리로 만든 것이다.

이에 대한 내용은 아래 포스팅을 참고하면 알 수 있다.

 

QueryDSL Repository 생성하기 (사용자 정의 리포지토리)

 

QueryDSL Repository 생성하기 (사용자 정의 리포지토리)

이번시간에는 QueryDSL을 사용하기위핸 QueryDSL 리포지토리를 만드는 방법에 대해 포스팅해보려고 한다. QueryDSL를 사용하기 위해서는 구현 클래스가 필요하고 Spring Data JPA는 인터페이스로 동작한다

hstory0208.tistory.com


Q 클래스 사용

QueryDSL을 적용하고 나면 생성되는 Q 클래스를 사용하는 방법은 총 2가지가 있다.

  1. new 키워드로 직접 생성해 별칭을 직접 지정하는 방법
  2. static import 하여 사용하는 방법.
// 1번에 해당하는 방법
QMember qMember = new QMember("m"); // m 이라는 별칭을 직접 지정하였음.
QMember qMember = QMember.member; // 기본 인스턴스 사용 (생성되어있는 Q클래스에 적용된 별칭사용) 

// 2번에 해당하는 방법.
import static study.querydsl.entity.QMember.member;

public List<Member> findAllMember() {
    return queryFactory
            .selectFrom(member)
            .fetch();
}

 

보틍은 2번 static import 하여 사용하는 방법을 많이 선호한다고 한다.

필자도 2번 방법이 코드가 깔끔해지고 추가적인 코드를 작성하지 않아도 되서 이 방법으로만 사용하려한다.


프로젝션

프로젝션이란, select 절에 지정한 대상을 말한다.

// 프로젝션 대상이 하나
List<String> result = queryFactory
    .select(member.username)
    .from(member)
    .fetch();

// 프로젝션 대상이 둘 이상
List<Tuple> result = queryFactory
    .select(member.username, member.age)
    .from(member)
    .fetch();

프로젝션 대상이 하나라면 타입을 명확하게 지정할 수 있지만 (위 예제에서는 username의 반환 타입이 String으로 제네릭을 String 반환 타입으로 지정),

프로젝션 대상이 둘 이상이라면 Tuple 타입이나, DTO 클래스를 반환할 수 있다.


QueryDSL에서 DTO 반환 방법

 

QueryDSL에서 DTO를 반환할 때 사용하는 방법은 여러가지 방법이 있지만

이 중에서 생성자를 사용하여 반환하는 방법과, @QueryProjection 어노테이션을 사용하는 방법에 대해 알아보고자 한다.

설명하기 앞서 설명에서 사용할 MemberDto는 다음과 같다.

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MemberDto {

    private String username;
    private int age;

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

 

생성자를 사용하여 반환하는 방법

QueryDSL이 지원하는 Projections 클래스의 constructor 메서드를 사용하여 DTO를 반환할 수 있다.

이 방법을 사용하기 위해서는 MemberDto의 생성자에 정의된 타입이 맞아야 하고, 순서도 맞아야 한다.

List<MemberDto> result = queryFactory
    .select(Projections.constructor(MemberDto.class,
    member.username,
    member.age))
    .from(member)
    .fetch();
}

 

 

@QueryProjection 어노테이션을 사용하는 방법

이 방법은 DTO의 생성자에 @QueryProjection 어노테이션을 붙여 DTO도 Q 클래스를 생성하여 생성된 QDTO 클래스를 사용하는 방법이다.

 

사용방법은 다음과 같다.

DTO의 생성자 위에 @QueryProjection 어노테이션을 추가해주고, gradle -> Tasks -> other -> compileJava를 실행해주자.

    @QueryProjection
    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }

그렇다면 아래 처럼 QMemberDTO가 생성되는것을 볼 수 있다.

 



이제 QMemberDto를 new 키워드로 다음과 같이 반환하면 된다.

List<MemberDto> result = queryFactory
    .select(new QMemberDto(member.username, member.age))
    .from(member)
    .fetch()

이 방법은 위 생성자를 사용해 반환하는 방법과 동일하게

@QueryProjection 어노테이션이 붙은 MemberDTO의 생성자의 파라미터 수와 타입을 일치시켜야 한다.

 

생성된 QMemberDto 코드를 보면 @QueryProjection 어노테이션이 붙은 생성자의 파라미터(username, age)의 타입과 이름이 그대로 들어가 있기 때문이다.

 

이 방법은 추가로 파라미터를 넣는다던가 오타를 내면, 컴파일러로 타입을 체크할 수 있기 때문에 안전하다는 장점이 있지만,

DTO가 순수하지 못하고 QueryDSL에 의존한다는 단점이 있다.


더 알아보기
 

QueryDSL 기본 문법 총정리 + 동적 쿼리

기본 문법 fetch() QueryDSL은 fetch를 이용해서 querydsl의 결과를 반환할 수 있다. fetch() 리스트로 결과를 반환하는 방법이다. 만약에 데이터가 없으면 빈 리스트를 반환해준다. List fetch = queryFactory .sele

hstory0208.tistory.com