오봉이와 함께하는 개발 블로그

Querydsl - 프로젝션 결과 반환 (DTO 조회) 본문

BE/JPA

Querydsl - 프로젝션 결과 반환 (DTO 조회)

오봉봉이 2022. 9. 18. 20:48
728x90

프로젝션과 결과 반환 - DTO 조회

먼저 순수 JPA에서 DTO 조회하는 방법을 보자.

순수 JPA에서 DTO 조회

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

    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }
}
@Test
void findDto() {
    List<MemberDto> result = em.createQuery("select new study.querydsl.dto.MemberDto(musername, m.age) " +
                    "from Member m", MemberDto.class)
            .getResultList();
    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}
memberDto = MemberDto(username=member1, age=10)
memberDto = MemberDto(username=member2, age=20)
memberDto = MemberDto(username=member3, age=30)
memberDto = MemberDto(username=member4, age=40)
  • 순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야 한다.
  • DTO의 package이름을 다 적어줘야해서 지저분하다.
  • 생성자 방식만 지원한다.

Querydsl 빈 생성(Bean population)

결과를 DTO로 반환할 때 사용하는 방법은 다음 3가지 방법이 있다.

  • 프로퍼티 접근
  • 필드 직접 접근
  • 생성자 사용

프로퍼티 접근 - Setter

com.querydsl.core.types.Projections를 사용한다.
단, 이 방법을 사용할 때는 DTO에 기본 생성자, Getter, Setter가 있어야 한다.

@Data
public class MemberDto {
    // ...............
    public MemberDto() {
    }
    // .........
}
    @Test
    void findDtoBySetter() {
        List<MemberDto> result = queryFactory
                .select(Projections.bean(MemberDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();
        for (MemberDto memberDto : result) {
            System.out.println("memberDto = " + memberDto);
        }
    }

필드 직접 접근

@Test
void findDtoByField() {
    List<MemberDto> result = queryFactory
            .select(Projections.fields(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();
    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}

Getter, Setter 모두 필요 없고 필드에 값을 다 꽂아서 사용한다.

필드 직접 접근 - 별칭이 다를 때

@Data
public class UserDto {
    private String name;
    private int age;
}
@Test
void findDtoByFieldAlias() {
    QMember memberSub = new QMember("memberSub");
    List<UserDto> fetch = queryFactory
            .select(Projections.fields(UserDto.class,
                    member.username.as("name"),
                    ExpressionUtils.as(
                            JPAExpressions
                                    .select(memberSub.age.max())
                                    .from(memberSub), "age")
                    )
            )
            .from(member)
            .fetch();
    for (UserDto userDto : fetch) {
        System.out.println("userDto = " + userDto);
    }
}
userDto = UserDto(name=member1, age=40) // 원래는 10
userDto = UserDto(name=member2, age=40) // 원래는 20
userDto = UserDto(name=member3, age=40) // 원래는 30
userDto = UserDto(name=member4, age=40)

서브 쿼리의 결과를 별칭에 매칭 시켜서 UserDtoage필드에 주입 시켜 출력한다.
("age"가 별칭을 지정해줌.)

  • 프로퍼티나, 필드 접근 생성 방식에서 이름이 다를 때 해결 방안
  • ExpressionUtils.as(source,alias)
    • 필드나 서브 쿼리에 별칭 적용
  • username.as("memberName")
    • 필드에 별칭 적용

생성자 사용

@Test
void findDtoByConstructor() {
    List<MemberDto> result = queryFactory
            .select(Projections.constructor(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();
    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}

이 방법은 MemberDto에 있는 usernameMember엔티티에 있는 username의 타입이 맞아야 실행 가능한 방법이다.(불러오는 필드의 타입이 모두 일치해야 한다.)

UserDto에 사용

@Data
public class UserDto {
    private String name;
    private int age;

    public UserDto() {
    }

    public UserDto(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
@Test
void findDtoByConstructor() {
    List<UserDto> result = queryFactory
            .select(Projections.constructor(UserDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();
    for (UserDto userDto : result) {
        System.out.println("userDto = " + userDto);
    }
}

파라미터가 있는 생성자(public UserDto(String name, int age))의 순서에 맞춰서 값이 주입되기 때문에 아래 처럼 member.age, member.username 순서로 작성하면 안 된다.

@Test
void findDtoByConstructor() {
    List<UserDto> result = queryFactory
            .select(Projections.constructor(UserDto.class,
                    member.age,
                    member.username))
            .from(member)
            .fetch();
    for (UserDto userDto : result) {
        System.out.println("userDto = " + userDto);
    }
}
인프런 김영한 지식공유자님 강의 : 실전! Querydsl
728x90
Comments