티스토리 뷰
컬렉션을 패치 조인하면 페이징이 불가능하다
- 컬렉션을 페치 조인하면 일대다 조인이 발생하므로 데이터가 예측할 수 없이 증가한다.
- 일대다에서 일(1)을 기준으로 페이징하는 것이 목적인데 다(N)을 기준으로 row가 생성된다.
- Order를 기준으로 페이징 하고 싶은데, 다(N)인 OrderItem을 조인하면 OrderItem이 기준이 되어버린다.
- 이 경우 하이버네이트는 경고 로그를 남기고 모든 DB 데이터를 읽어서 메모리에서 페이징을 시도한다. 최악의 경우 장애로 이어질 수 있다.
그럼 어떻게 페이징 + 컬렉션 엔티티를 함께 조회해야할까?
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();
}
- 레퍼지토리에 jpql 쿼리를 하나 만들었다. 이번에는 toOne 관계 애들만 페치조인한다. toMany가 없기 때문에 정상적으로 페이징이 되어 값이 나온다(row도 정상).
application.yml
spring: #띄어쓰기 없음
datasource: #띄어쓰기 2칸
url: jdbc:h2:tcp://localhost/~/jpashop #4칸
username: sa
password:
driver-class-name: org.h2.Driver
jpa: #띄어쓰기 2칸
hibernate: #띄어쓰기 4칸
ddl-auto: create #띄어쓰기 6칸
properties: #띄어쓰기 4칸
hibernate: #띄어쓰기 6칸
#show_sql: true #띄어쓰기 8칸
format_sql: true #띄어쓰기 8칸
default_batch_fetch_size: 100
logging.level: #띄어쓰기 없음
org.hibernate.SQL: debug #띄어쓰기 2칸
org.hibernate.type: trace #띄어쓰기 2칸
- default_batch_fetch_size를 추가해준다.
@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); //toOne
List<OrderDto> result = orders.stream().map(o -> new OrderDto(o)).collect(Collectors.toList());
return result;
}
@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){
orderId = order.getId();
name = order.getMember().getName();
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress();
orderItems = order.getOrderItems().stream().map(orderItem -> new OrderItemDto(orderItem)).collect(Collectors.toList());
}
}
@Getter
static class OrderItemDto{
private String itemName;
private int orderPrice;
private int count;
public OrderItemDto(OrderItem orderItem){
itemName = orderItem.getItem().getName();
orderPrice = orderItem.getOrderPrice();
count = orderItem.getCount();
}
}
- v3_page를 작동시키는데 처음 조회시에는 위의 레퍼지토리를 참고해보면 orderItem 과 item을 불러오지 않는다.
- DTO로 변환할 때에 값을 꺼내게 되어 LAZY 조회가 시작된다.
- 해당 로그를 확인해보자
select i1_0.item_id,i1_0.dtype,i1_0.name,i1_0.price,i1_0.stock_quantity,i1_0.artist,i1_0.etc,i1_0.author,i1_0.isbn,i1_0.actor,i1_0.director
from item i1_0 where i1_0.item_id in (1,2,3,4);
- 이런 식으로 default_batch_fetch_size로 인해 in 쿼리가 발생하며 단 한번의 쿼리 조회로 값을 가져오게 된다. (orderitem도 쿼리 한개 날아간다.) (100으로 해두었으니 in은 100개가 최대로 나가며 부족할 경우 다시 100개를 처리한다)
- 페이징 처리가 가능해졌고, 트래픽처리도 되었다.
- 하이버네이트 6의 경우 100개를 불러오면 나머지는 NULL로 나가게 된다.
엔티티 내부에서 따로 적용도 가능하다.
toMany
@BatchSize(size = 100)
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL) //cascade는 여러개를 한 번에 해줌.. 무슨말인지 잘모르겠다
private List<OrderItem> orderItems = new ArrayList<>();
toOne
@BatchSize(size = 100)
@Entity
@Getter
@Setter
public class Member {
@Id @GeneratedValue
김영한 선생님은 yml에서 잘 활용해서 사용하는 걸 추천하시는 듯 하다.
toMany가 들어갈 경우 페이징 및 성능 최적화 방법
- toOne으로만 불러온다
- default_batch_fetch_size를 적용
- toMany에 대한 초기화
순으로 적용하면 성능이 최적화 된다.
- 맥시멈은 default_batch_fetch_size 1000개 이다.
- 권장은 100~1000개이다.
- 하지만 100이든 1000이든 전체 데이터를 로딩해야 하므로 메모리 사용량이 같다.
- 1000으로 설정하는 것이 성능상 가장좋지만,결국 DB든 애플리케이션이든 순간 부하를 어디까지 견딜 수 있는지로 결정하면 된다.
- Total
- Today
- Yesterday
- 컨트
- SpringBoot
- 리터럴
- 향해플러스
- React
- react실행
- reject
- Intercepter
- rejectValue
- exception
- HTTP
- ArgumentResolver
- 로그인
- 백엔드 개발자 역량
- BindingResult
- 항해99
- JPA
- hypertexttransferprotocol
- 예외처리
- 향해플러스백엔드
- 스프링부트
- 향해99
- jpa api
- filter
- Java
- 항해플러스
- 스프링공부
- thymleaf
- 인터셉터
- 백엔드 개발자 공부
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |