Spring/QueryDSL

프로젝션과 결과 반환 - @QueryProjection

느리지만 꾸준하게 2022. 4. 21. 01:15

궁극의 방법 QueryProjection 그것을 알아보자!

 

 

MemberDto에다가 바로 QueryProjection이라고 적어주자,

@Data
@NoArgsConstructor
public class MemberDto {


    // username이랑 age 두개만 최적화해서 가져오고 싶다해서 MemberDto를 만들어줌
    private String username;
    private int age;

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

 

그리고 gradle 파일에가서 compileQuerydsl을 눌러주면 dto도 Q파일로 생성이 된다.

 

 

Test에서는 아래와 같이 해준다.

@Test
public void findDtoByQueryProjection() {
    queryFactory
            .select(new QMemberDto(member.username, member.age))
            .from(member)
            .fetch();
}

 

 

expression이 딱딱 맞기 때문에 컴파일 시점에 타입이 안맞으면 오류를 내준다.

public QMemberDto(com.querydsl.core.types.Expression<String> username, com.querydsl.core.types.Expression<Integer> age) {
    super(MemberDto.class, new Class<?>[]{String.class, int.class}, username, age);
}

 

돌려보면 결과가 잘 나온다.

@Test
public void findDtoByQueryProjection() {
    List<MemberDto> result = queryFactory
            .select(new QMemberDto(member.username, member.age))
            .from(member)
            .fetch();

    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}
/* select
        member1.username,
        member1.age 
    from
        Member member1 */ select
            member0_.username as col_0_0_,
            member0_.age as col_1_0_ 
        from
            member member0_


memberDto = MemberDto(username=member1, age=10)
memberDto = MemberDto(username=member2, age=20)
memberDto = MemberDto(username=member3, age=30)
memberDto = MemberDto(username=member4, age=40)

 

 

 

constructor는 오류를 냈을 때 실행하는 순간이 되서야 오류를 찾을수가 있다. 런타임 오류다.

@Test
public void findDtoByConstructor() {
    // member의 username과 age랑 타입을 맞춰야 한다.
    List<UserDto> result = queryFactory
            .select(Projections.constructor(UserDto.class,
                    member.username,
                    member.age,
                    member.id))
            .from(member)
            .fetch();

    // DTO 조회하는게 간편하다.
    for (UserDto UserDto : result) {
        System.out.println("UserDto = " + UserDto);
    }
}

 

똑같은 방식을 프로젝션으로 하면 컴파일오류가 바로 보인다.

@Test
public void findDtoByQueryProjection() {
    List<MemberDto> result = queryFactory
            .select(new QMemberDto(member.username, member.age, member.id))
            .from(member)
            .fetch();

    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}

왜냐 Querydsl이 만들어 놓은 생성자에는 type으로 두가지만 받아들이게 되어서 그렇다.

그래서 다른 인자가 추가로 들어오거나 하면 에러가 나게 된다.

굉장히 잘 설계가 되어있다.(Command + p를 눌려서 이름도 확인해주자.) 매우 편리하다.

public QMemberDto(com.querydsl.core.types.Expression<String> username, com.querydsl.core.types.Expression<Integer> age) {
    super(MemberDto.class, new Class<?>[]{String.class, int.class}, username, age);
}

 

하지만 단점이 Q파일을 생성해야 하고 즉 MemberDto에 @QueryProjection을 넣어줘야 하고 

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

 

@Test
public void findDtoByQueryProjection() {
    List<MemberDto> result = queryFactory
            .select(new QMemberDto(member.username, member.age))
            .from(member)
            .fetch();

    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}

 

그리고 MemberDto에서 @QueryProjection을 넣는 순간 memberdto에서 querydsl에 대한 의존성을 가지게 된다. 

querydsl 라이브러리를 뺀다고 하면 아래 코드들이 다 영향을 받을 것이다.

// MemberDto


package study.querydsl.dto;


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

@Data
@NoArgsConstructor
public class MemberDto {


    // username이랑 age 두개만 최적화해서 가져오고 싶다해서 MemberDto를 만들어줌
    private String username;
    private int age;

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

 

 

Dto는 보통 리포지토리에서 Dto를 조회한 다음에 서비스에서도 쓰고 컨트롤러에서도 쓰고 api를 바로 반환하기도 한다.

여러 Layer에 걸쳐서 돌아다닌다. 그렇게 흘러가는 Dto안에 @QueryProjection이 들어가 있는 것이다.

(순수한 Dto가 아닌 Querydsl에 의존적이다.)

 

 

프로젝션 결과반환을 전체적으로 정리를 해보자.

 

  • 결과가 하나인 거는 타입으로 바로 지정하면 된다.

  • 결과가 둘 이상일때는 튜플을 써야했고
  • 그리고 tuple은 가급적 리포지토리 계층안에서만 쓰고 

 

  • DTO 조회할때는 순수 JPA에서는 new operation을 써야한다.

 

 

 

  • Querydsl은 세가지 방법을 지원한다.
  • setter - 프로퍼티 접근방법
  • 필드에 직접 접근
  • 생성자 constructor 사용하는 방법 

그리고 생성자를 사용하는 방식에서는 QueryProjection 방식까지 지원해주고 

Dto도 Q파일로 생성을 해서

 

 

query를 날릴때 아래와 같이 활용을 할 수가 있다.

대신 DTO가 Querydsl annotation에 의존하게 된다.

 

마지막으로 참고로 distinct도 알아두자.

 

 

 

 

 

 

 

 

<출처 김영한: 실전! Querydsl >

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

 

실전! Querydsl - 인프런 | 강의

Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, - 강의 소개 | 인프런...

www.inflearn.com