상세 컨텐츠

본문 제목

[SPRING] Querydsl | 프로젝션과 결과 반환

JAVA/기본 & 강의복습

by ranlan 2021. 5. 17. 00:50

본문

728x90

조회 기본

select 대상이 하나일 때 타입을 명확하게 지정할 수 있음

List<String> result = queryFactory
                .select(member.username)
//                .select(member.username).distinct() JPQL의 distinct와 동일
                .from(member)
                .fetch();

select 대상이 여러 개일 때

import com.querydsl.core.Tuple;

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

 

DTO로 조회

MemberDto

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

* @Data

Equivalent to @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode.

기본적으로 getter, setter 등을 만들어주지만 따로 생성자를 만들었기 때문에 기본 생성자를 정의하거나 혹은 @NoArgsConstructor을 선언해줘야 한다.

 

 

1) JPQL

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

DTO의 package 경로까지 모두 명시해야 하며 new 명령어를 사용해 생성자 방식만 지원

 

2) Querydsl 빈 생성(Bean population)

import com.querydsl.core.types.Projections;

1) 프로퍼티 접근

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

기본 생성자와 getter, setter가 있어야하며  setter를 통해 값이 주입된다.

 

2) 필드 직접 접근

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

getter, setter 없이 바로 값이 주입된다.

 

3) 생성자 사용

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

기본 생성자와 사용자 정의 생성자가 있어야 하며 생성자의 인자 타입이 맞아야 한다. 인자가 맞지 않는 경우 RUNTIME ERROR가 발생한다.

 

※ 별칭이 다를 때

UserDto

@Data
@NoArgsConstructor
public class UserDto {

    private String name; // username(Member) -> name(UserDto)
    private int age;

    public UserDto(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

예1) 필드 직접 접근 방식

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

실행 결과

userDto = UserDto(name=null, age=10)
userDto = UserDto(name=null, age=20)
userDto = UserDto(name=null, age=30)
userDto = UserDto(name=null, age=40)

예2) 서브쿼리

나이는 최대 나이로 통일하여 모든 member의 이름 조회

import com.querydsl.core.types.ExpressionUtils;

QMember memberSub = new QMember("memberSub");

List<UserDto> result2 = queryFactory
            .select(Projections.fields(UserDto.class,
                    // member.username.as("name"), // 두 방법 다 가능
                    ExpressionUtils.as(member.username, "name"),
                    ExpressionUtils.as(JPAExpressions 
                        .select(memberSub.age.max())
                        .from(memberSub), "age") 
                    ))
            .from(member)
            .fetch();

예3) 생성자 사용

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

 

3) @QueryProjection

MemberDto

import com.querydsl.core.annotations.QueryProjection;

@Data
public class MemberDto {
    private String username;
    private int age;

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

Gradle > Querydsl > other > compileQuerydsl > DTO Qtype 생성 확인

 

dto.QMemberDto

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

 

생성자를 이용한 방법은 런타임 오류가 발생하는 반면 QueryProjection은 컴파일러로 타입을 체크할 수 있어 보다 안전하고 인텔리제이의 자동완성 기능을 활용할 수 있다.

하지만 DTO도 Qtype을 생성해야 하고 여러 레이어에 거쳐 사용되는 DTO가 queryDSL에 의존성을 갖게 되어 순수한 DTO로써 활용하지 못한다는 단점이 있다. 또한 아키텍쳐적으로 깔끔하지 않다.

 

 

728x90

관련글 더보기

댓글 영역