조회 하는 엔티티가 컬렉션을 가지고 있으면 컬렉션의 수 만큼 데이터가 뻥튀기된다.
2개의 Order를 json으로 출력하려했지만 Order가 가진 컬렉션 OrderItem때문에 데이터가 뻥튀기되어(Order x OrderItem만큼 뻥튀기됨) 쓸데없는 데이터 중복을 일으키고 말았다.
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();
}
[
{
"orderId": 4,
"name": "userB",
"orderDate": "2022-09-21T10:56:46.800634",
"orderStatus": "ORDER",
"address": {
"city": "진주",
"zipcode": "2222",
"street": "2"
},
"orderItems": [
{
"itemName": "JPA1 BOOk",
"orderPrice": 10000,
"count": 1
},
{
"itemName": "JPA2 BOOK",
"orderPrice": 40000,
"count": 2
}
]
},
{
"orderId": 4,
"name": "userB",
"orderDate": "2022-09-21T10:56:46.800634",
"orderStatus": "ORDER",
"address": {
"city": "진주",
"zipcode": "2222",
"street": "2"
},
"orderItems": [
{
"itemName": "JPA1 BOOk",
"orderPrice": 10000,
"count": 1
},
{
"itemName": "JPA2 BOOK",
"orderPrice": 40000,
"count": 2
}
]
},
{
"orderId": 11,
"name": "userA",
"orderDate": "2022-09-21T10:56:46.837536",
"orderStatus": "ORDER",
"address": {
"city": "서울",
"zipcode": "1111",
"street": "1"
},
"orderItems": [
{
"itemName": "SPRING1 BOOK",
"orderPrice": 60000,
"count": 3
},
{
"itemName": "SPRING2 BOOK",
"orderPrice": 160000,
"count": 4
}
]
},
{
"orderId": 11,
"name": "userA",
"orderDate": "2022-09-21T10:56:46.837536",
"orderStatus": "ORDER",
"address": {
"city": "서울",
"zipcode": "1111",
"street": "1"
},
"orderItems": [
{
"itemName": "SPRING1 BOOK",
"orderPrice": 60000,
"count": 3
},
{
"itemName": "SPRING2 BOOK",
"orderPrice": 160000,
"count": 4
}
]
}
]
하지만 distinct 옵션을 주면 Order와 OrderItems가 단순한 조인으로 나온 결과처럼 Order X OrderItems 개수 만큼 나오는 것이아닌 Order안에 OrderItems의 갯수 만큼 추가하여 쓸데없는 데이터 중복을 없애준다.
하지만 실제 sql에서는 대부분 Order X OrderItems 개수만큼 가져오는 것은 동일하다(완전히 같은 것의 중복은 안가져오지만), 다만 getResultList부분에서 중복된 부분을 없애주고 일반 join 처럼 Order X OrderItems 만큼 Order객체를 result로 내보내는게 아닌 Order 객체안에 OrderItems를 컬렉션으로 넣어서 내보내 준다.
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();
}
[
{
"orderId": 4,
"name": "userB",
"orderDate": "2022-09-21T11:05:03.110142",
"orderStatus": "ORDER",
"address": {
"city": "진주",
"zipcode": "2222",
"street": "2"
},
"orderItems": [
{
"itemName": "JPA1 BOOk",
"orderPrice": 10000,
"count": 1
},
{
"itemName": "JPA2 BOOK",
"orderPrice": 40000,
"count": 2
}
]
},
{
"orderId": 11,
"name": "userA",
"orderDate": "2022-09-21T11:05:03.157016",
"orderStatus": "ORDER",
"address": {
"city": "서울",
"zipcode": "1111",
"street": "1"
},
"orderItems": [
{
"itemName": "SPRING1 BOOK",
"orderPrice": 60000,
"count": 3
},
{
"itemName": "SPRING2 BOOK",
"orderPrice": 160000,
"count": 4
}
]
}
]
✔ 하지만 크나큰 단점이 있는데 그건 toMany관계일때 join fetch를 처리했다면 페이징처리를 할 수 가 없다. 왜냐하면 toMany관계페이징 처리 옵션인 firstResult,maxResult를 주면 메모리에서 처리하기 때문이다.
(애초에 db서버에 Order x OrderItems 개수만큼의 데이터를 가져오는 쿼리를 날린다. 그리고 받아온 데이터를 웹어플리케이션 서버가 중복을 제거(order x orderItems 만큼 객체를 만드는게 아닌 Order안에 OrderItems만큼의 컬렉션을 넣어주는것)한다)
왜 메모리에서 처리하냐면 distinct 결국 조인을 통해서 OrderItem만큼 레코드를 가져온 후 처리하는 것이기 때문이다.
따라서 OrderItem의 개수를 모르는 상황에서 Order로 페이징 처리를 할 수 없다. 결국 메모리에 OrderItem만큼의 레코드를 적재하고 중복을 제거한 후에(order에 OrderItem을 적재한후에) 페이징 처리를 할 수 밖에 없는 것이다.
따라서 만약 Order를 페이징처리하려했다면 결과적으로 모든 Order X OrderItems를 쿼리를 통해 메모리에 가져오게 되는데 이 부분이 문제라는 것이다. 예를 들어 order가 10개 OrderItems가 100개라고 생각해보자
Order:1 OrderItems:1
.
.
.
Order:1 OrderItems:10
Order:2 OrderItems:11
.
.
.
Order:2 OrderItems:20
.
.
order 10 OrderItem 91
.
.
order 10 OrderItem 100
Order 10개를 가져오기 위해 우리는 100개의 레코드 조회하여 가져왔고 distinct옵션이 있고 페이징처리를 하려 했기때문에 웹어플리케이션 서버에서 100개의 레코드(OrderItem)를 각각에 맞는 Order에 담고 Order의 갯수만큼 페이징 처리를 하는 과중한 업무를 맞겨버리게 된것이다.
만약 이러한 과중한 업무를 10000명이 사용한다면? 컬렉션 개수가 1000개였다면? 어마어마한 부하를 서버에게 주고 만다.
따라서 toMany에서 페이징 처리를 하는 곳에는 fetch를 사용하면 안된다.
다만 해결방법은 있다.
https://keyboardfoot.tistory.com/96