페이징 + 컬렉션 엔티티를 조회하려면 어떻게 해야할까
데이터가 뻥튀기 되지 않게 할수 없을까???
[
{
"orderId": 1,
"name": "userA",
"orderDate": "2025-07-30T18:23:28.899788",
"orderStatus": "ORDER",
"address": {
"city": "서울",
"street": "1",
"zipcode": "1111"
},
"orderItems": [
{
"itemName": "JPA1 BOOK",
"orderPrice": 10000,
"count": 1
},
{
"itemName": "JPA2 BOOK",
"orderPrice": 20000,
"count": 2
}
]
},
{
"orderId": 2,
"name": "userB",
"orderDate": "2025-07-30T18:23:28.945129",
"orderStatus": "ORDER",
"address": {
"city": "진주",
"street": "2",
"zipcode": "2222"
},
"orderItems": [
{
"itemName": "SPRING1 BOOK",
"orderPrice": 20000,
"count": 3
},
{
"itemName": "SPRING2 BOOK",
"orderPrice": 40000,
"count": 4
}
]
}
]
이렇게 order 를 기준으로 페이징을 할 경우는 어떻게 해야할까
쉽게 말해서 Order 엔티티는 OrderItem 과 일대다 관계이기 때문에
fetch join 을하고 페이징이 불가능하다.
먼저 ToOne(OneToOne, ManyToOne) 관계를 모두 페치조인 한다. ToOne 관계는 row수를 증가시키지 않 으므로 페이징 쿼리에 영향을 주지 않는다.
컬렉션은 지연 로딩으로 조회한다
지연 로딩 성능 최적화를 위해 hibernate.default_batch_fetch_size , @BatchSize 를 적용한다.
-hibernate.default_batch_fetch_size: 글로벌 설정
- @BatchSize: 개별 최적화
- 이 옵션을 사용하면 컬렉션이나, 프록시 객체를 한꺼번에 설정한 size 만큼 IN 쿼리로 조회한다
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 1000
장점
쿼리 호출 수가 1 + N 1 + 1 로 최적화 된다
조인보다 DB 데이터 전송량이 최적화 된다. (Order와 OrderItem을 조인하면 Order가 OrderItem 만큼 중복해서 조회된다. 이 방법은 각각 조회하므로 전송해야할 중복 데이터가 없다.)
페치 조인 방식과 비교해서 쿼리 호출 수가 약간 증가하지만, DB 데이터 전송량이 감소한다.
컬렉션 페치 조인은 페이징이 불가능 하지만 이 방법은 페이징이 가능하다.
결론
ToOne 관계는 페치 조인해도 페이징에 영향을 주지 않는다. 따라서 ToOne 관계는 페치조인으로 쿼리 수 를 줄이고 해결하고, 나머지는 hibernate.default_batch_fetch_size 로 최적화 하자.
객체 그래프 탐색을 하다보면 알아서 batch fetch size 에 따라서 쿼리가 in 쿼리로 한꺼번에 가져올 것이다.
XXToOne -> DTO로 바로 조회, Fetch Join
XXToMany -> 페이징 필요없고 하나만 하는거면 컬렉션 페치조인 (최소한개만), 페이징할꺼면 다 XXXToOne Fetch 로 다가져오고, 나머지는 그냥 객체 그래프 탐색으로 지연로딩 + batch fetch size
참고로 컬렉션 페치 조인일때는 DTO 로 해결하지말자.
그리고 DTO 로 조회역시 SQL 을 짜는것과 유사하기 때문에 select 절에 컬렉션이 오는것 자체가 불가능해서 다른 방법을 해야한다.
대부분의 문제는 위에서 말한 엔티티를 직접 조회해서 DTO 로 변환하자.
그리고 컬렉션 fetch join 은 페이징을 안할꺼면 상관이 없지만 , 1개만 사용하자.
그리고 2개 이상사용해야하거나, 페이징을 해야되는 상황이면
ToOne 만 fetch join 하고 ToMany 는 default_batch_fetch_size 로 해결하자.