[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이 알아서 번역해서 제대로 쿼리가 들어감을 확인할 수 있다