[QueryDSL] 프로젝션, DTO 조회
2022. 8. 28. 14:47ㆍLanguage`/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이 알아서 번역해서 제대로 쿼리가 들어감을 확인할 수 있다