[Spring] 23.06.07 QueryDsl 프로젝션과 결과 Dto로 반환하기 (3)

hyewon jeong·2023년 6월 7일
0

TIL

목록 보기
131/138

1 . 문제점

queryDsl의 반환값으로 프로젝션이 여러개일때는 튜플 또는 DTO를 통해 결과값은 반환 받는다.

queryDsl 공부하며 memberDto클래스를 만들고 Dto로 결과를 반환하는 방법 중 첫번째 방법인 setter를 이용했는데 에러가 발생했다.

package study.querydsl3.dto;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Setter
@ToString
public class MemberDto {

  private String username;
  private int age;

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

    List<MemberDto> result = jpaQueryFactory
        .select(Projections.bean(MemberDto.class, member.username,member.age))
        .from(member)
        .fetch();
    for (MemberDto s : result) {
      System.out.println("s = " + s);
    }
  }

에러 내용

com.querydsl.core.types.ExpressionException: class com.querydsl.core.types.QBean cannot access a member of class study.querydsl3.dto.MemberDto with modifiers "protected"

2 . 원인

QueryDSL의 Projections.bean() 메서드는 기본 생성자와 프로퍼티의 getter/setter를 필요로 합니다. MemberDto 클래스의 기본 생성자 protected로 지정되어 있기 때문에 해당 메서드가 접근할 수 없는 것입니다.


3 . 해결

Projections.constructor() 메서드를 이용해 MemberDto 클래스의 생성자를 호출하고, member.username과 member.age 값을 전달하여 MemberDto 객체를 생성합니다. 이렇게 생성된 DTO 객체들은 결과로 반환되어 result 리스트에 저장됩니다.


4 . 알게 된점

4-1. QueryDsl을 이용하여 프로젝션이 둘 이상 일때 결과를 Dto로 반환하는 방법 4가지

4-1-1.프로퍼티 setter 이용

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

4-1-2.필드 이용

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

4-1-3.생성자 이용

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

프로퍼티 나, 필드 접근 생성 방식에서 이름이 다를 때 해결 방안

ExpressionUtils.as(source,alias) : 필드나, 서브 쿼리에 별칭 적용 username.as("memberName") : 필드에 별칭 적용

  @Test
  void projection_dto_QueryDsl_As(){

    QMember qMember = new QMember("q");
    List<UserDto> result = jpaQueryFactory
        .select(Projections.bean(UserDto.class,
            member.username.as("user"),
            ExpressionUtils.as(JPAExpressions.select(qMember.age.max()).from(qMember),"age")))
        .from(member)
        .fetch();
  • 프로퍼티 접근과 필드 접근은 필드네임이 정확히 같아야 한다. 만약 다를 경우 .as() 등을 사용한다.
  • 생성자 접근 타입으로 매칭이 되기 때문에 필드네임과 상관없이 원하는 필드의 순서만 맞으면 된다.

4-1-4. @QueryProjection 이용

  1. Dto 생성자에 @QueryProjection 적용 후 큐클래스 만들기 위해 compileJava 또는 compileQueryDsl 클릭한다.
@NoArgsConstructor//(access = AccessLevel.PROTECTED)
@Getter
@Setter
@ToString
public class MemberDto {

  private String username;
  private int age;
@QueryProjection
  public MemberDto(String username, int age) {
    this.username = username;
    this.age = age;
  }
}
  1. QMemberDto 생성 확인후 코드 작성
  @Test
  void projection_dto_QueryProjection() {

    jpaQueryFactory
        .select(new QMemberDto(member.username, member.age))
        .from(member)
        .fetch();
  }

이 방법은 컴파일러로 타입을 체크할 수 있으므로 가장 안전한 방법이다. 다만 DTO에 QueryDSL 어노테 이션을 유지해야 하는 점과 DTO까지 Q 파일을 생성해야 하는 단점이 있다.

profile
개발자꿈나무

0개의 댓글