-> 블로그 이전

[QueryDSL] 프로젝션, DTO 조회

2022. 8. 28. 14:47Language`/JPA

프로젝션

프로젝션이란 "Select절에 조회 대상을 지정"하는 것이다

 

대상 하나

조회대상이 하나라면 Return Type은 해당 조회 대상의 Type으로 정해진다

// Projection [Member]
List<Member> fetch1 = query.select(member)
.from(member)
.where(member.age.goe(25))
.fetch();
// Projection [String]
List<String> fetch2 = query.select(member.username)
.from(member)
.where(member.age.goe(25))
.fetch();
// Projection [Integer]
List<Integer> fetch3 = query.select(member.age)
.from(member)
.where(member.age.goe(25))
.fetch();

 

대상 여러개 (Tuple)

QueryDSL에서는 프로젝션 대상으로 여러 필드를 선택하게 되면 Tuple이라는 "Map과 비슷한" Type을 return해준다

List<Tuple> fetch = query.select(member.username, member.age)
.from(member)
.fetch();
for (Tuple tuple : fetch) {
System.out.println("Name = " + tuple.get(member.username) + " -> Age = " + tuple.get(member.age));
}

 

빈 생성

쿼리에 대한 결과를 엔티티가 아닌 "특정 객체(DTO..)"로 받고 싶다면 Bean Population을 사용한다

 

1) 프로퍼티 접근

프로퍼티 접근은 결과에 대한 DTO에 "필드의 getter/setter"를 생성하고 Projections.bean()을 통해서 결과를 받아오면 된다

  • 첫번째 파라미터 = 결과에 대한 클래스 (ItemDto.class)
  • 두번째/세번째/.... 파라미터 = 매핑될 필드

Projections.bean()은 "Setter"를 사용해서 값을 채우게 된다

그리고 파라미터가 없는 생성자(access = Public)가 "반드시" 필요하다

// MemberOrderDto
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberOrderDto {
private Long memberId;
private Long orderId;
private String memberName;
private Integer memberAge;
private String orderProductName;
private Integer orderAmount;
}
List<MemberOrderDto> fetch = query.select(
Projections.bean(
MemberOrderDto.class,
order.member.id.as("memberId"),
order.id.as("orderId"),
order.member.username.as("memberName"),
order.member.age.as("memberAge"),
order.product.name.as("orderProductName"),
order.orderAmount.as("orderAmount")
)
).from(order)
.innerJoin(order.member)
.innerJoin(order.product)
.fetch();
for (MemberOrderDto memberOrderDto : fetch) {
System.out.println(memberOrderDto);
}

>> 쿼리 결과 ↔ 매핑할 DTO의 프로퍼티명이 다르다면 "반드시" as를 통해서 별칭을 설정해줘야 한다

 

2) 필드 직접 접근

필드 직접 접근은 Projections.fileds()를 통해서 "필드에 직접 접근"해서 값을 채워준다

  • 여기서 필드를 private로 설정해도 제대로 동작한다

그리고 파라미터가 없는 생성자(access = Public)가 "반드시" 필요하다

List<MemberOrderDto> fetch = query.select(
Projections.fields(
MemberOrderDto.class,
order.member.id.as("memberId"),
order.id.as("orderId"),
order.member.username.as("memberName"),
order.member.age.as("memberAge"),
order.product.name.as("orderProductName"),
order.orderAmount.as("orderAmount")
)
).from(order)
.innerJoin(order.member)
.innerJoin(order.product)
.fetch();
for (MemberOrderDto memberOrderDto : fetch) {
System.out.println(memberOrderDto);
}

 

3) 생성자 사용

생성자를 사용한 DTO조회는 Projections.constructor()를 통해서 DTO의 생성자를 사용해서 값을 return하게 된다

  • 여기서 지정한 프로젝션과 생성자의 파라미터 순서는 "정확하게" 일치해야한다
  • 파라미터가 없는 생성자가 없어도 "필드에 대한 생성자"만 정확하게 있으면 동작한다
List<MemberOrderDto> fetch = query.select(
Projections.constructor(
MemberOrderDto.class,
order.member.id.as("memberId"),
order.id.as("orderId"),
order.member.username.as("memberName"),
order.member.age.as("memberAge"),
order.product.name.as("orderProductName"),
order.orderAmount.as("orderAmount")
)
).from(order)
.innerJoin(order.member)
.innerJoin(order.product)
.fetch();
for (MemberOrderDto memberOrderDto : fetch) {
System.out.println(memberOrderDto);
}

 

@QueryProjection

DTO클래스의 생성자 레벨에 @QueryProjection 애노테이션을 붙이게 되면 DTO 클래스도 QType이 생성되고 이를 그대로 결과로 매핑시켜서 받아오면 된다

  • 기본적인 Projections을 통해서 DTO 조회보다 TypeSafe하지만, 물론 QueryDSL에 더 종속적이다는 단점도 존재한다
@Data
public class MemberOrderDto {
private Long memberId;
private Long orderId;
private String memberName;
private Integer memberAge;
private String orderProductName;
private Integer orderAmount;
@QueryProjection
public MemberOrderDto(Long memberId, Long orderId, String memberName, Integer memberAge, String orderProductName, Integer orderAmount) {
this.memberId = memberId;
this.orderId = orderId;
this.memberName = memberName;
this.memberAge = memberAge;
this.orderProductName = orderProductName;
this.orderAmount = orderAmount;
}
}
List<MemberOrderDto> fetch = query.select(
new QMemberOrderDto(
order.member.id,
order.id,
order.member.username,
order.member.age,
order.product.name,
order.orderAmount
)
).from(order)
.innerJoin(order.member)
.innerJoin(order.product)
.fetch();
for (MemberOrderDto memberOrderDto : fetch) {
System.out.println(memberOrderDto);
}

  • as를 통해서 프로젝션 필드에 대한 Alias를 붙여주지 않아도 DTO QType이 알아서 번역해서 제대로 쿼리가 들어감을 확인할 수 있다