티스토리 뷰
이제 1:N 관계의 컬랙션을 조회할 때의 방법을 알아보자.
가장 쉽지만 가장 좋지 않은 방법인 entity에 직접 조회하는 방법이 있다.
@GetMapping("/api/v1/orders")
public List<Order> ordersV1(){
List<Order> all = orderRepository.findAllByCriteria(new OrderSearch());
for(Order order: all){
order.getMember().getName();
order.getDelivery().getAddress();
List<OrderItem> orderItems = order.getOrderItems();
for(OrderItem orderItem : orderItems){
orderItem.getItem().getName();
}
}
return all;
}
그리고 이것을 DTO를 통해서 해결하는 방법이 다음 방법으로 생각해볼 수 있다.
@GetMapping("/api/v2/orders")
public List<OrderDto> ordersV2() {
return orderRepository.findAllByCriteria(new OrderSearch()).stream()
.map(OrderDto::new)
.collect(Collectors.toList());
}
@Data
static class OrderDto{
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
private List<OrderItemDto> orderItems;
public OrderDto(Order order) {
this.orderId = order.getId();
this.name = order.getMember().getName();
this.orderDate = order.getOrderDate();
this.orderStatus = order.getStatus();
this.address = order.getDelivery().getAddress();
order.getOrderItems().forEach(o -> o.getItem().getName());
this.orderItems = order.getOrderItems().stream()
.map(orderItem -> new OrderItemDto(orderItem))
.collect(Collectors.toList());
}
}
해당 DTO에서 설명을 덧붙이자면 orderItems의 경우에도 별도의 DTO로 분리해서 완전히 entity와의 의존관계를 없애는 것이 좋다.
그리고 생성자에서 order.getOrderItems().forEach(o -> o.getItem().getName()); 해당 부분은 lazy loading을 시켜주고
넣어주는 방법을 택했다.
앞선 fetch join부분에서 설명했듯 여기까지 완성을 했다면, N+1 문제에 직면하게 된다.
이것을 우리는 fetch join을 해결할 수 있었다.
OrderRepository.java
public List<Order> findAllWithItem() {
return em.createQuery("select o from Order o"+
" join fetch o.member m"+
" join fetch o.delivery d"+
" join fetch o.orderItems oi"+
" join fetch oi.item i", Order.class)
// .setFirstResult(1)
// .setMaxResults(100)
.getResultList();
}
하지만 이렇게 조회하게 되면, DB의 조회 원리상 우리가 알고 싶은 orderId를 기준으로 orderItems를 모두 조회하기 때문에
N배만큼 중복되어 row가 나오게 된다.
이것을 해결하기 위해서는 distinct를 사용하면 해결할 수 있다.
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"+
" join fetch oi.item i", Order.class)
// .setFirstResult(1)
// .setMaxResults(100)
.getResultList();
}
또한, 주석으로 해놓은 페이징 부분은 메모리에서 sorting을 하게 된다.
왜냐하면 JPA에서 distinct의 로직을 해결하고, 마지막에 db에 select query를 날려주기 때문이다.
이러한 점에서 페이징을 하게 된다면 기존의 sorting기준이 틀어지게 된다.
또한, 메모리상에서 페이징을 하게 되어 데이터가 많을 경우에 서버에 부하를 많이 주게 된다.
1:N fetch join의 경우에는 절대 페이징을 하면 안된다.
이것을 해결하기 위해서는 fetch join으로 ToOne 관계를 가져오도록 하고,
OrderItems(1:N관계)는 지연로딩으로 가져오면 된다.
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();
}
@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> collect = orders.stream()
.map(OrderDto::new)
.collect(Collectors.toList());
return collect;
}
그리고 orderItem으로 인해서 Query가 많이 날아가는 것은 batch옵션을 통해서 해결할 수 있다.
batch 옵션을 사용하면 batch size만큼의 Query를 한번에 날려주게 된다.
application.yml
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 100
batch는 보통 100개에서 1000개 사이 정도로 생각하면 된다.
JVM의 힙메모리는 어차피 전체 데이터를 로딩해야하기 때문에 기존과 동일하다고 볼 수 있다.
'Back End > JPA' 카테고리의 다른 글
[JPA 기초] 2. Entity - DB 맵핑 (2) | 2022.10.04 |
---|---|
[JPA 기초] 1. 영속성 컨텍스트 (0) | 2022.10.03 |
[API] 4. JPA 조회 성능 최적화(N+1문제와 fetch join) (0) | 2022.09.05 |
[API] 3. Spring Data JPA API - 조회 (0) | 2022.08.30 |
[API] 2. Spring Data JPA API - 수정 (0) | 2022.08.30 |
- Total
- Today
- Yesterday
- KAFKA
- React
- broker
- frontend
- consumer
- spring boot
- API
- 리액트
- Data Engineering
- Front
- docker
- Linux
- rhel
- cs
- spring
- centos
- Producer
- Container
- OS
- Firebase
- Java
- JPA
- apache kafka
- zookeeper
- 프론트엔드
- feign client
- logback
- apache
- K8S
- NextJS
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |