반응형
250x250
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
Tags
- fetch join
- 친절한 SQL 튜닝
- @MappedSuperclass
- 스프링 컨테이너
- 오라클
- TCP/IP
- DTO
- 데이터베이스
- 정렬
- 데이터모델링
- 엔티티
- JPA
- JPQL
- 컬렉션 조회 최적화
- 성능최적화
- 페이징
- 스프링 데이터 JPA
- querydsl
- 스프링
- 그룹함수
- INDEX SCAN
- Spring data JPA
- 값 타입
- Spring
- index
- SQL
- 서브쿼리
- 페치조인
- 자바의 정석
- SQL 튜닝
Archives
- Today
- Total
nu_s
[JPA 활용] 컬렉션 조회 최적화 - JPA에서 DTO 직접 조회 🐱 본문
728x90
반응형
V4. JPA에서 DTO 직접 조회하기
- 앞에서는 엔티티를 DTO로 변환해서 컬렉션으로 반환했다.
- 이번에는 DTO를 그대로 반환해서 JPA가 DTO를 직접 조회하게 만들어 보겠다.
@RestController
@RequiredArgsConstructor
public class OrderApiController {
private final OrderQueryRepository orderQueryRepository;
@GetMapping("/api/v4/orders")
public List<OrderQueryDto> orderV4() {
return orderQueryRepository.findOrderQueryDtos();
}
}
jpabook.jpashop.repository.order.query.OrderQueryDto
@Data
public class OrderQueryDto {
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
private List<OrderItemQueryDto> orderItems;
public OrderQueryDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address) {
this.orderId = orderId;
this.name = name;
this.orderDate = orderDate;
this.orderStatus = orderStatus;
this.address = address;
}
}
jpabook.jpashop.repository.order.query.OrderItemQueryDto
@Data
public class OrderItemQueryDto {
@JsonIgnore
private Long orderId;
private String itemName;
private int orderPrice;
private int count;
public OrderItemQueryDto(Long orderId, String itemName, int orderPrice, int count) {
this.orderId = orderId;
this.itemName = itemName;
this.orderPrice = orderPrice;
this.count = count;
}
}
- JPA에서 직접 조회하는 방식을 사용하기 위해 OrderQueryDto와 OrderItemQueryDto를 생성했다.
- 기존 Controller에 있으면 Repository가 Controller를 참조해서 의존관계가 순환이 된다.
- 따라서 패키지를 jpabook.jpashop.repository 안에 새로 생성했다.
jpabook.jpashop.repository.order.query.OrderQueryRepository
public List<OrderQueryDto> findOrderQueryDtos() {
List<OrderQueryDto> result = findOrders();
result.forEach(o -> {
List<OrderItemQueryDto> orderItems = findOrderItems(o.getOrderId());
o.setOrderItems(orderItems);
});
return result;
}
- findOrderQueryDtos 에서는 Order를 조회하고 DTO로 바꿔주고 Order의 수만큼 OrderItem도 DTO로 변환해준다.
private List<OrderQueryDto> findOrders() {
return em.createQuery(
"select new jpabook.jpashop.repository.order.query.OrderQueryDto(o.id, m.name, o.orderDate, o.status, d.address)" +
" from Order o" +
" join o.member m" +
" join o.delivery d", OrderQueryDto.class)
.getResultList();
}
- findOrders에서는 ToOne 관계인 member, delivery를 fetch join으로 한번에 가져온다.
private List<OrderItemQueryDto> findOrderItems(Long orderId) {
return em.createQuery(
"select new jpabook.jpashop.repository.order.query.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)" +
" from OrderItem oi" +
" join oi.item i" +
" where oi.order.id = :orderId", OrderItemQueryDto.class)
.setParameter("orderId", orderId)
.getResultList();
}
- findOrderItems 에서는 orderItem에서 item을 fetch 조인으로 가져온다.
- item은 Order와는 XXXToMany 관계이지만, orderItem과는 XXXToOne 관계이므로 페치조인이 가능하다.
쿼리 실행 수
- findOrderQueryDtos()를 통해 실행되는 쿼리의 수를 세보자
- findOrders()에서 Order, Member, Delivery를 조인하여 불러오는 쿼리 1번
- findOrderItems()에서 OrderItem, Item을 조인하여 불러오는 쿼리 N번
- 현재 DB에 주문이 2건 있으므로 1 + 2 = 3 총 3번의 쿼리가 실행된다.
- 이번에도 N + 1 문제가 발생한 것을 볼 수 있다.
- 이 방법 또한 최적화가 필요하다.
V5. JPA에서 DTO 직접 조회 - 컬렉션 조회 최적화
@RestController
@RequiredArgsConstructor
public class OrderApiController {
private final OrderQueryRepository orderQueryRepository;
@GetMapping("/api/v5/orders")
public List<OrderQueryDto> orderV5() {
return orderQueryRepository.findAllByDto_optimization();
}
}
findAllByDto_optimization()
public List<OrderQueryDto> findAllByDto_optimization() {
//모든 주문 조회
List<OrderQueryDto> result = findOrders();
//모든 주문의 주문번호를 List로 가져온다.
List<Long> orderIds = result.stream()
.map(o -> o.getOrderId())
.collect(Collectors.toList());
//orderIds로 모든 OrderItem을 조회한다.
List<OrderItemQueryDto> orderItems = em.createQuery(
"select new jpabook.jpashop.repository.order.query.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)" +
" from OrderItem oi" +
" join oi.item i" +
" where oi.order.id in :orderIds", OrderItemQueryDto.class)
.setParameter("orderIds", orderIds)
.getResultList();
//groupingby를 통해 Map으로 변환한다.
Map<Long, List<OrderItemQueryDto>> orderItemMap = orderItems.stream()
.collect(Collectors.groupingBy(OrderItemQueryDto::getOrderId));
result.forEach(o -> o.setOrderItems(orderItemMap.get(o.getOrderId())));
return result;
}
V4와의 차이점
- findAllByDto_optimization() 내에 작성된 쿼리를 보면 where절에 '='가 아니라 'in'이 들어가있다.
- orderId에 해당하는 데이터를 하나씩 쿼리로 실행하는 것이 아니라, 모든 orderId를 조회해서 그에 해당하는 주문이 있다면 다 가져오는 것이다.
정리
- 쿼리는 루트 1번, 컬렉션 1번 -> 총 2번 실행된다.
- XXXToOne 관계를 먼저 조회하고, 식별자 orderId를 얻어 XXXToMany 관계인 OrderItem을 한꺼번에 조회한다.
- Map을 사용해서 매칭 성능을 향상시켰다.
출처 : 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
728x90
반응형
'JPA' 카테고리의 다른 글
[JPA 활용] OSIV와 성능 최적화 🐱 (0) | 2023.11.20 |
---|---|
[JPA 활용] 컬렉션 조회 최적화 - 플랫 데이터 🐱 (0) | 2023.11.13 |
[JPA 활용] 컬렉션 조회 최적화 - 페치 조인 & 페이징(Batch Size)🐱 (0) | 2023.11.11 |
[JPA 활용] 컬렉션 조회 최적화 - 엔티티를 DTO로 변환 🐱 (0) | 2023.11.10 |
[JPA 활용] 조회 성능 최적화 - JPA에서 DTO로 조회🐱 (0) | 2023.11.10 |