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 | 31 |
Tags
- consumer
- centos7
- QueryDSL
- Streams
- CodePipeline
- topic생성
- transactionaleventlistener
- JPA
- Kubernetes
- API
- bean
- CI
- PAGING
- AWS
- entity graph
- mysql
- Kotlin
- mirror maker2
- Spring JPA
- ECS
- K8s
- producer
- git
- Entity
- offsetdatetime
- kafka
- spring kafka
- spring
- cd
- Spring Data JPA
Archives
- Today
- Total
Yebali
Spring을 이용한 API 개발 - Collection 조회 최적화 본문
Collection조회란 Entity에서 일대다 관계(@OneToMany)를 조회하는 것이다.
fetch join을 사용한 컬렉션 조회
Controller
@GetMapping("/api/v3/orders")
public List<OrderDto> ordersV3() {
List<Order> orders = orderRepository.findAllWithItem();
List<OrderDto> result = orders.stream()
.map(o -> new OrderDto(o))
.collect(toList());
return result;
}
Repository
public List<Order> findAllWithItem() {
return em.createQuery(
"select distinct o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d" +
" join fetch o.orderItems oi" + //Collection fecth join
" join fetch oi.item i", Order.class)
.getResultList();
}
OrderItems까지 fetch join으로 한 번에 조회한다. -> SQL이 1번만 실행된다.
'distinct'를 사용하지 않으면 일대다(OneToMany) 조회 시 조회 결과가 카테시안 곱이 되어 조회된다. JPA의 distinct는 SQL에도 distinct를 추가하고 Order 엔티티가 조회되면 애플리케이션에서 중복을 제거하고 orderItems는 Collection에 모아준다.
이 방법의 가장 큰 단점은 페이징이 불가능하다는 것이다.
Collection fetch join을 했을 때 페이징을 하면, 모든 데이터를 DB에서 읽은 후 메모리에서 페이징 처리한다.
그리고 Collection fetch join은 1개만 사용해야 한다. 둘 이상의 fetch join을 사용하면 데이터가 부정합 하게 조회될 수 있다.
+ 페이징 처리
페이징 처리와 + Collection 조회를 같이하는 것은 아래 방법으로 대부분 가능하다.
- *ToOne관계를 모두 fecth join 한다.
- Collection은 지연 로딩으로 조회한다.
- 지연 로딩 성능 최적화를 위해 'hibernate.default_batch_fetch_size'나 '@BatchSize'를 적용한다.
hibernate.default_batch_fetch_size : 글로벌 설정
@BatchSize : 개별 최적화
위의 옵션을 사용하면 Collection이나 proxy객체를 한꺼번에 설정한 size만큼 IN 쿼리로 조회한다.
Controller
@GetMapping("/api/v3.1/orders")
public List<OrderDto> ordersV3_page(@RequestParam(value = "offset", defaultValue = "0") int offset,
@RequestParam(value = "limit", defaultValue = "100") int limit) {
List<Order> orders = orderRepository.findAllWithMemberDelivery(offset, limit);
List<OrderDto> result = orders.stream()
.map(o -> new OrderDto(o))
.collect(toList());
return result;
}
Repository
@Repository
public class OrderRepository {
public List<Order> findAllWithMemberDelivery(int offset, int limit) {
return em.createQuery(
"select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d", Order.class)
.setFirstResult(offset)
.setMaxResults(limit)
.getResultList();
}
}
*ToOne관계는 fetch join으로 최적화.
application.yml
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 100
장점
- 쿼리 호출 수가 N+1 -> 1+1로 최적화된다.
- Join보다 DB 데이터 전송량이 최적화된다.
- fetch join에 비해 쿼리 호출 수가 약간 증가하지만 전체적인 DB 데이터 전송량이 감소한다.
- 페이징이 가능하다.
ToOne관계는 fetch join을 해도 페이징에 영향을 주지 않는다.
따라서 ToOne관계는 fetch join으로 쿼리 수를 줄이고 나머지는 위의 옵션으로 해결하기를 추천한다.
JPA에서 DTO로 바로 조회, 플랫 데이터 최적화
Controller
@GetMapping("/api/v6/orders")
public List<OrderQueryDto> ordersV6() {
//모든 데이터를 join해서 하나로 받은 후
List<OrderFlatDto> flats = orderQueryRepository.findAllByDto_flat();
//개발자가 직접 API 스펙에 맞는 DTO에 매핑함.
return flats.stream()
.collect(groupingBy(o -> new OrderQueryDto(o.getOrderId(), o.getName(), o.getOrderDate(), o.getOrderStatus(), o.getAddress()),
mapping(o -> new OrderItemQueryDto(o.getOrderId(), o.getItemName(), o.getOrderPrice(), o.getCount()), toList())
)).entrySet().stream()
.map(e -> new OrderQueryDto(e.getKey().getOrderId(),
e.getKey().getName(), e.getKey().getOrderDate(), e.getKey().getOrderStatus(),
e.getKey().getAddress(), e.getValue()))
.collect(toList());
}
Repository
public List<OrderFlatDto> findAllByDto_flat() {
return em.createQuery(
"select new" +
" jpabook.jpashop.repository.order.query.OrderFlatDto(o.id, m.name, o.orderDate, o.status, d.address, i.name, oi.orderPrice, oi.count)" +
" from Order o" +
" join o.member m" +
" join o.delivery d" +
" join o.orderItems oi" +
" join oi.item i", OrderFlatDto.class)
.getResultList();
}
장점
- 쿼리 한 번에 모든 조회가 끝나버린다.
단점
- 쿼리는 한 번이지만 join으로 인해 더 느릴 수 있다.
- 애플리케이션에서 데이터를 일일이 DTO에 매핑하는 추가 작업이 크다.
- 페이징이 불가능하다.
그냥 참고
양방향 연관관계 조회 시 무한루프에 걸리지 않게 하려면 한 곳에 '@JsonIgnore'를 추가해야 할 수 있다.
'Spring' 카테고리의 다른 글
Spring Data JPA란? (0) | 2021.10.11 |
---|---|
Spring JPA의 OSIV (0) | 2021.10.11 |
Spring을 이용한 API개발 - 조회 성능 최적화 (0) | 2021.10.11 |
Spring을 이용한 API개발 - 기본 (0) | 2021.10.11 |
Spring JPA 준영속 엔티티 수정하기 (0) | 2021.10.11 |