[QueryDSL] 중급 문법 ①

kiteB·2021년 12월 5일
0

QueryDSL

목록 보기
5/9
post-thumbnail

[ 프로젝션과 결과 반환 - 기본 ]

💡 프로젝션

: SELECT 절에 조회할 대상을 지정하는 것

1. 프로젝션 대상이 하나

List<String> result = queryFactory
        .select(member.username)
        .from(member)
        .fetch();

프로젝션 대상이

  • 하나인 경우, 타입을 명확하게 지정할 수 있다.
  • 둘 이상이면, 튜플이나 DTO로 조회해야 한다.

2. 튜플 조회

프로젝션 대상이 둘 이상일 때 사용

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

for (Tuple tuple : result) {
    String username = tuple.get(member.username);
    Integer age = tuple.get(member.age);
    System.out.println("username=" + username);
    System.out.println("age=" + age);
}
  • Tuple은 리포지토리 계층 안에서 쓰는 정도는 괜찮지만 그 밖의 계층에서는 DTO로 변환하는 것이 좋다.

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

1. 순수 JPA에서 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;
    }
}

순수 JPA에서 DTO 조회 코드

List<MemberDto> result = em.createQuery(
        "select new study.querydsl.dto.MemberDto(m.username, m.age) " + 
                "from Member m", MemberDto.class)
        .getResultList();
  • 순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야 한다.
  • DTO의 package명을 다 적어줘야 해서 지저분하다.
  • 생성자 방식만 지원한다.

2. Querydsl 빈 생성 (Bean population)

결과를 DTO 반환할 때 사용하며, 3가지 방법을 지원한다.

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

1) 프로퍼티 접근 - Setter

List<MemberDto> result = queryFactory
        .select(Projections.bean(MemberDto.class,
                member.username,
                member.age))
        .from(member)
        .fetch();
  • Projections.bean(주입대상클래스, 프로퍼티1, 프로퍼티2) 방식으로 값을 주입한다.

2-1) 필드 직접 접근

List<MemberDto> result = queryFactory
        .select(Projections.fields(MemberDto.class,
                member.username,
                member.age))
        .from(member)
        .fetch();
  • Getter, Setter가 없어도 필드에 바로 값을 주입한다.
  • Projections.fields(주입대상클래스, 필드1, 필드2)

2-2) 별칭이 다를 때

  • UserDto
@Data
public class UserDto {
    private String name;
    private int age;
}
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();
  • 프로퍼티나, 필드 접근 생성 방식에서 이름이 다를 때 해결방안!
  • ExpressionUtils.as(source, alias): 필드나, 서브 쿼리에 별칭을 적용한다.
  • username.as("memberName"): 필드에 별칭 적용

2-3) 생성자 사용

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

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

1. 생성자 + @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;
    }
}
  • DTO 프로젝션에 사용될 생성자에 @QueryProjection을 붙여준다.
  • 이를 통해 DTO로 Q타입으로 생성해준다! → QMemberDto 생성 확인!

2. @QueryProjection 활용

List<MemberDto> result = queryFactory
        .select(new QMemberDto(member.username, member.age))
        .from(member)
        .fetch();
  • 이 방법은 컴파일러로 타입을 체크할 수 있어 가장 안전한 방법!
  • DTO에 QueryDSL 어노테이션을 유지해야 하고 DTO까지 Q파일을 생성해야 한다는 단점이 있다.

3. distinct 사용

List<String> result = queryFactory
        .select(member.username).distinct()
        .from(member)
        .fetch();
  • JPQL의 distinct와 같다.
profile
🚧 https://coji.tistory.com/ 🏠

0개의 댓글