티스토리 뷰
N+1이란?
조회 시 1개의 쿼리만을 생각하고 설계했지만, 추가로 N개의 쿼리가 발생하는 문제를 일컫는다.
- 즉시로딩(EAGER)의 경우: User 전체를 조회할 때, 각 User의 필드를 채우기 위해 즉시 조회하여 N+1 발생
- 지연로딩(LAZY)의 경우: User 조회 후 필드에 접근할 때, N+1 발생
fetch join으로 해결하기
즉시로딩에서는 커스텀할 수 있는 부분이 존재하지 않기 때문에, 지연로딩 과정에서 바로 사용을 할 객체에 대해서는 조정이 필요한데, 이때 해결책으로 fetch join을 사용한다.
fetch join의 한계점
fetch join은 ~ToMany 관계(컬렉션)가 1개 이하일 때만 가능하며, 이 때 페이징이 가능하지만(distinct 키워드를 사용).. 권장하지 않는다.
- 일반적으로 ~ToMany 관계에 있는 엔티티끼리 join을 하게 되면, 결과 row 수가 뻥튀기 된다(중복 데이터 발생)
- 따라서, 페이징의 기준으로 가져갈 컬럼이 불명확해지고
(컬렉션이 2개 이상이 되면 n*m개가 되면서 데이터가 부정합하게 조회될 수 있음) - 하이버네이트는 경고 로그를 남기면서, 메모리 내에서 페이징을 한다..!
- 이때, OOM(Out Of Memory)예외가 발생할 수 있어 위험하다
이렇게 정리할 수 있다.
(한계점 1) 컬렉션(~ToMany) fetch join 시 페이징은 위험하다
(한계점 2) 둘 이상의 컬렉션(~ToMany)은 fetch join이 불가능하다
1) 컬렉션(~ToMany) fetch join 시 페이징은 위험하다
fetch join 대신 지연로딩을 유지하고, hibernate.default_batch_fetch_size 옵션(global 하게 작용)을 사용하거나, 대상 필드 또는 클래스에 @BatchSize을 적용하여 개별 최적화한다.
* size 크기는 100~1000 사이를 권장 (DB에 따라 IN절 파라미터를 1000으로 제한하기도 하므로)
- 이 옵션을 사용하면 컬렉션이나, 프록시 객체를 한꺼번에 설정한 size만큼 where절 IN 쿼리로 조회한다
- 따라서 쿼리 호출 수가 N번 만큼 무수히 날아가지는 않음 (설정한 size에 비례하여 날아감)
→ fetch join 방식과 비교해서 쿼리 호출 수가 약간 증가하지만, DB 데이터 전송량이 감소 - 데이터가 명확하므로 쿼리를 통한 페이징이 가능하며,
- 중복 데이터 또한 없다!
※ 주의해야할 점!
@BatchSize에 fetch join을 걸면 안된다.
fetch join이 우선시되어 적용되기 때문에 batch size가 무시된다!
2) 둘 이상의 컬렉션(~ToMany)은 fetch join이 불가능하다
두 컬렉션의 자료형을 Set으로 변경하여 중복 자체를 허용하지 않게끔 하면, MultipleBagFetchException(둘 이상의 컬렉션 fetch join을 막는 exception)없이 정상적으로 모든 데이터를 가져온다.
* 이 때, 데이터의 순서를 보장하려면 LinkedHashSet 사용
하지만 여전히 페이징은 위험하다(메모리에서 진행됨)
→ 페이징을 하고 싶다면 1)처럼 fetch join 대신 지연로딩을 유지하고 @BatchSize 옵션을 통해 최적화해야한다.
'Spring' 카테고리의 다른 글
컴포넌트 스캔과 수동 Bean 등록 (0) | 2022.04.24 |
---|---|
OSIV (0) | 2022.04.24 |
JPA의 병합(merge)과 변경 감지(Dirty Checking) 차이점 (0) | 2022.04.21 |
도메인 모델 패턴 / 트랜잭션 스크립트 패턴 (0) | 2022.04.21 |
의존 관계 주입(Dependency Injection) 방법 (0) | 2022.04.20 |