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)
.getResultList();
}
실행되는 쿼리는 아래와 같다.
order와 orderItem 만 집중해서 봐보자.
4개의 행이 반환된다.
로그를 찍어보면 다음과 같이 나온다.
public List<OrderDto> ordersV3() {
List<Order> orders = orderRepository.findAllWithItem();
for (Order order : orders) {
System.out.println("order ref = " + order + " id = " + order.getId());
}
우리가 원하는것은 중복이 제거된 order 2개만을 원하는것이다.
* 해결1
distinct 키워드를 달아주면 어떨까?
db에 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)
.getResultList();
}
기대한대로 2개만 반환되었다.
쿼리를 찍어보자
예상과 달리 db에서는 행이 4개가 만들어 졌다.
db의 distinct는 한줄한줄의 data가 전부 똑같아야 제거가 된다!
비밀은, jpa에서 order의 id가 같으면, 중복을 제거해주었다는 점이다.
즉, jpql의 distinct는 아래 2가지 역할을 한다.
1) db에 distinct 쿼리 보내기
2) 같은 id 같은 엔티티가 조회되면, 애플리케이션에서 중복을 제거
* 문제2
페이징 불가능
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();
}
페이징로직을 추가하고 실제로 실행되는 쿼리를 살펴보자
limit, offset이 없다 (???)
selectdistinct order0_.order_id as order_id1_6_0_,
member1_.member_id as member_i1_4_1_,
delivery2_.delivery_id as delivery1_2_2_,
orderitems3_.order_item_id as order_it1_5_3_,
item4_.item_id as item_id2_3_4_,
order0_.delivery_id as delivery4_6_0_,
order0_.member_id as member_i5_6_0_,
order0_.order_date as order_da2_6_0_,
order0_.status as status3_6_0_,
member1_.city as city2_4_1_,
member1_.street as street3_4_1_,
member1_.zipcode as zipcode4_4_1_,
member1_.name as name5_4_1_,
delivery2_.city as city2_2_2_,
delivery2_.street as street3_2_2_,
delivery2_.zipcode as zipcode4_2_2_,
delivery2_.status as status5_2_2_,
orderitems3_.count as count2_5_3_,
orderitems3_.item_id as item_id4_5_3_,
orderitems3_.order_id as order_id5_5_3_,
orderitems3_.order_price as order_pr3_5_3_,
orderitems3_.order_id as order_id5_5_0__,
orderitems3_.order_item_id as order_it1_5_0__,
item4_.name as name3_3_4_,
item4_.price as price4_3_4_,
item4_.stock_quantity as stock_qu5_3_4_,
item4_.artist as artist6_3_4_,
item4_.etc as etc7_3_4_,
item4_.author as author8_3_4_,
item4_.isbn as isbn9_3_4_,
item4_.actor as actor10_3_4_,
item4_.director as directo11_3_4_,
item4_.dtype as dtype1_3_4_
from
orders order0_
innerjoinmember member1_
on order0_.member_id=member1_.member_id
innerjoin
delivery delivery2_
on order0_.delivery_id=delivery2_.delivery_id
innerjoin
order_item orderitems3_
on order0_.order_id=orderitems3_.order_id
innerjoin
item item4_
on orderitems3_.item_id=item4_.item_id
이유
limit offset은 행의 앞을 생략하고 가져오는 것이다
N인 order_item기준으로 data가 뻥튀기 되버렸기 때문에
아래의 행에서 limit, offset 명령어 만으로 중복을 제거하고 유니크한 order만 가져올수 있는 방법이 없다.
그래서 jpa는 db의 data를 통째로 애플리케이션으로 가져온후, 경고를 남기고 메모리에서 페이징 해버린다!
이는 ,Out of memory 의 원인이 된다.
2025-03-09 19:11:11.061 WARN 9556 --- [nio-8080-exec-1] o.h.h.internal.ast.QueryTranslatorImpl : HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
데이터가 작은경우는 문제가 없지만,
주문 1개에 10000개 의 데이터가 있는경우만해도 장애로 이어질수 있다. (사용자가 N명이라면 10000*N개의 data)