본문 바로가기

JPA

[Querydsl] 프로젝션

프로젝션이란?

select 절에 어떤 것을 가져올지 결정하는 것을 프로젝션이라고 한다.

 

- 프로젝션 대상이 하나인 경우 타입을 명확하게 정할 수 있다.

// String 값인 member.username 하나만 조회해 올때
List<String> result = queryFactory
          .select(member.username)
          .from(member)
          .fetch();

 

- 프로젝션 대상이 둘 이상인 경우 튜플이나 DTO로 조회해온다.

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

이때 Tuple은 Querydsl에서 제공하는 Tuple이기 때문에 repository 계층에서 사용하는 것은 괜찮지만 service나 controller 계층에서 사용하는 것은 좋은 설계가 아니다. 그렇기 때문에 앞단으로 넘길때는 Tuple보다는 DTO로 변환해서 넘기는 것을 권장한다.

DTO 조회

순수 JPA에서 DTO 조회

List<MemberDto> result = em.createQuery(
          "select new study.querydsl.dto.MemberDto(m.username, m.age) " +
                  "from Member m", MemberDto.class)
          .getResultList();

순수 JPA에서 DTO 조회를 수행할 때는 new 명령어를 사용한다. 이 방법은 DTO의 패키지명까지 전부 적어줘야 하고 생성자 방식만 지원한다는 단점이 있다. 하지만 Querydsl은 이런 단점을 총 3가지 방법을 지원해주면서 해결할 수 있다.

 

Querydsl에서 DTO조회

1. 프로퍼티 접근 - setter

DTO 클래스에 setter가 필요하다.

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

 

2. 필드 직접 접근

DTO 클래스 필드에 직접 값을 기입하기 때문에 setter가 필요없다.

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

엔티티의 컬럼명과 DTO의 필드 이름이 다른경우 as를 이용하여 별칭을 지정해주어야 한다.

List<MemberDto> result = queryFactory
                .select(Projections.fields(MemberDto.class,
                        member.username.as("name"),
                        member.age))
                .from(member)
                .fetch();

 

3. 생성자 사용

생성자 방식은 DTO 생성자의 파라미터와 순서를 맞춰주어야 한다.

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

@QueryProjection

DTO 클래스도 Q타입으로 생성하는 방법으로 DTO 생성자에 @QueryProjection 어노테이션을 붙여서 사용한다. 이 방법은 컴파일 시점에 타입체크를 해주기 때문에 안전한 방법이지만 DTO 클래스에서 Querydsl 어노테이션을 사용하기 때문에 순수했던 DTO 클래스가 Querydsl에 의존성을 갖게 된다는 점과 DTO 클래스까지 Q타입으로 생성해주어야 한다는 단점이 존재한다.

 

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

 

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