Java / / 2023. 1. 13. 15:00

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

반응형

해당 글은 김영한 님의 querydsl을 수강하며 정리하려고 적는 포스팅입니다.

서론

Member엔티티를 조회하는데 거기서 username과 age만 가지고 오고 싶은 상황이라고 가정하겠다.

 

 

먼저 순수 JPA에서 조회를 알아보자.

 

순수 JPA

 

1. MemberDto

package study.querydsl.dto;

import lombok.Data;

@Data
public class MemberDto {

    private String username;
    private int age;

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

 

2. 순수 JPA에서 DTO 조회 코드

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

    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}
//------------------------------------------------------------------
2023-01-13T14:12:07.970+09:00 DEBUG 11844 --- [           main] org.hibernate.SQL                        : 
    /* select
        new study.querydsl.dto.MemberDto(m.username, m.age) 
    from
        Member m */ select
            m1_0.username,
            m1_0.age 
        from
            member m1_0
            
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는 Type이 달라서 조회하여 가지고 올 수가 없다.
  • DTO의 package 이름을 다 적어줘야 해서 지저분하다.
  • 생성자 방식만 지원한다. (MemberDto의 생성자에 값들이 넘어오게 된다.)

다음은 Querydsl을 이용한 조회이다. (Querydsl 빈 생성 - Bean population)

 

Querydsl 빈 생성 - Bean population

 

결과를 DTO 반환할 때 사용한다.

아래 3가지 방법을 지원한다.

 

  1. 프로퍼티 접근
  2. 필드 직접 접근
  3. 생성자 사용

 

1. 프로퍼티 접근 - setter

@Test
public 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);
    }
}

//----------------------------------------------------
2023-01-13T14:22:27.160+09:00 DEBUG 8660 --- [           main] org.hibernate.SQL                        : 
    /* select
        member1.username,
        member1.age 
    from
        Member member1 */ select
            m1_0.username,
            m1_0.age 
        from
            member m1_0
            
memberDto = MemberDto(username=member1, age=10)
memberDto = MemberDto(username=member2, age=20)
memberDto = MemberDto(username=member3, age=30)
memberDto = MemberDto(username=member4, age=40)

쿼리 결과로 부터 bean을 생성하고, Projections.bean() 메서드를 호출하여 사용한다.

setter 기반으로 값을 bean에 injection 해준다.

 

따라서 MemberDto의 기본생성자를 만들어 주어야 한다.

(기본생성자를 만들고 set을 진행하기 때문에 없으면 오류가 발생하게 된다.)

 

 

2. 필드 직접 접근 - setter 필요 없음

@Test
public 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);
    }
}
-----------------------------------------------------------
2023-01-13T14:38:03.228+09:00 DEBUG 19316 --- [           main] org.hibernate.SQL                        : 
    /* select
        member1.username,
        member1.age 
    from
        Member member1 */ select
            m1_0.username,
            m1_0.age 
        from
            member m1_0
            
memberDto = MemberDto(username=member1, age=10)
memberDto = MemberDto(username=member2, age=20)
memberDto = MemberDto(username=member3, age=30)
memberDto = MemberDto(username=member4, age=40)

getter, setter 필요 없이 MemberDto 필드에 값이 주입된다.

 

 

3. 생성자 접근 방법

@Test
public 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);
    }
}
-------------------------------------------------------
2023-01-13T14:40:32.199+09:00 DEBUG 7028 --- [           main] org.hibernate.SQL                        : 
    /* select
        member1.username,
        member1.age 
    from
        Member member1 */ select
            m1_0.username,
            m1_0.age 
        from
            member m1_0
            
memberDto = MemberDto(username=member1, age=10)
memberDto = MemberDto(username=member2, age=20)
memberDto = MemberDto(username=member3, age=30)
memberDto = MemberDto(username=member4, age=40)

타입이 정확이 맞아야만 한다.

 


※ 별칭이 다를 경우

UserDto

package study.querydsl.dto;

import lombok.Data;

@Data
@NoArgsConstructor
public class UserDto {
	//Member entity와 별칭이 다름
    private String name;
    private int age;
    
    public UserDto(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

 

@Test
public void findUserDto() {
    List<UserDto> result = queryFactory
            .select(Projections.fields(UserDto.class,
                    member.username,
                    //member.username.as("name"),
                    //ExpressionUtils.as(member.username, "name")도 가능하지만 너무 지저분하다.
                    member.age))
            .from(member)
            .fetch();

    for (UserDto userDto : result) {
        System.out.println("userDto = " + userDto);
    }
}

----------------------------------------------------------
2023-01-13T14:43:58.174+09:00 DEBUG 19464 --- [           main] org.hibernate.SQL                        : 
    /* select
        member1.username,
        member1.age 
    from
        Member member1 */ select
            m1_0.username,
            m1_0.age 
        from
            member m1_0
            
userDto = UserDto(name=null, age=10)
userDto = UserDto(name=null, age=20)
userDto = UserDto(name=null, age=30)
userDto = UserDto(name=null, age=40)
  • 프로퍼티나, 필드 접근 생성 방식에서 이름이 다를 경우 null이 들어간다.
  • 생성자는 상관없음
    • 위에 생성자 접근 방법에서 MemberDto만 UserDto로 변경해 주면 된다.
  • ExpressionUtils.as(source,alias) : 필드나, 서브 쿼리에 별칭 적용
    • 서브 쿼리는 별칭이 없기 때문에 ExpressionUtils.as( source, alias )에 alias를 사용하여 별칭을 주어 매칭시켜야 한다.
    • 서브쿼리는 별 방법이 없기 때문에 ExpressionUtils를 사용해야만 한다.
  • username.as("memberName") : 필드에 별칭 적용
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유