N+1문제?
연관 관계가 설정된 엔티티 사이에서 한 엔티티를 조회하였을 때,
조회된 엔티티의 개수(n)만큼 연관된 엔티티를 조회하기 위해 추가적인 쿼리가 발생하는
문제를 의미한다.
•
언제 발생하는가?
◦
JPA Repository를 활용해 인터페이스 메서드를 호출할 때
•
누가 발생시키는가?
◦
1:N 또는 N:1 관계를 가진 엔티티를 조회할 때 발생
•
어떤 상황에 발생되는가?
◦
JPA Fetch 전략이 EAGER 전략으로 데이터를 조회하는 경우
◦
JPA Fetch 전략이 LAZY 전략으로 데이터를 가져온 이후에 연관 관계인 하위 엔티티를 다시 조회하는 경우
•
왜 발생하는가?
◦
JPA Repository로 find 시 실행하는 첫 쿼리에서 하위 엔티티까지 한 번에 가져오지 않고,
하위 엔티티를 사용할 때 추가로 조회하기 때문에
◦
JPQL은 기본적으로 글로벌 Fetch전략을 무시하고 JPQL만 가지고 SQL을 생성하기 때문에
EAGER인 경우
1.
JPQL에서 만든 SQL을 통해 데이터 조회
2.
이후 JPQ에서 Fetch 전략을 가지고 해당 데이터의 연관 관계인 하위 엔티티들을 추가 조회
3.
2번의 과정으로 N+1문제 발생
LAZY인 경우
1.
JPQL에서 만든 SQL을 통해 데이터를 조회
2.
JPA에서 Fetch 전략을 가지지만, 지연 로딩이기 때문에 추가 조회는 하지 않음
3.
하지만 하위 엔티티를 가지고 작업하게 되면 추가 조회가 발생하기 때문에 결국 N+1문제 발생
해결방법
1. Fetch Join
•
N+1 문제 자체가 발생하는 이유는 한쪽 테이블만 조회하고 연결된 다른 테이블은 따로 조회하기 때문
•
미리 두 테이블을 Join하여 한번에 모든 데이터를 가져올 수 있다면 애초에 N+1문제가 발생하지 않을 것
•
그렇게 나온 해결 방법이 FetchJoin 방법이다.
Fetch Join의 단점
•
쿼리 한번에 모든 데이터를 가져오기 때문에 JPA가 제공하는 Pageable 사용 불가
→ batch size로 해결 : 즉시로딩이나 지연로딩 시에 연관된 엔티티를 조회할 때 지정한 size만큼 sql의 in절을 사용해서 조회하는 방식
•
1:N 관계가 두 개 이상인 경우 사용 불가
→ MultipleBagFetchException 발생
•
패치 조인 대상에게 별칭 부여 불가능
•
쿼리문을 따로 작성해야함
2. @EntityGraph
•
EntityGraph 상에 있는 엔티티들의 연관 관계 속에서 필요한 엔티티와 컬렉션을 함께 조회하려고 할 때 사용한다
•
outerJoin 사용
•
attributePaths에 쿼리 수행 시 바로 가져올 필드명을 지정하면 LAZY가 아닌 EAGER조회로 가져오게 된다.
@EntityGraph(attributePaths = {"필드명"})
List<User> findAllEntityGraph();
Java
복사
@EntityGraph의 단점
•
Fetch Join의 경우 inner join을 하지만 EntityGraph는 outer Join을 기본으로 한다.
→ 기본적으로 outer join 보다 inner join이 성능 최적화에 더 유리하다.
Fetch Join과 EntityGraph 사용시 주의할 점
FetchJoin과 EntityGraph는 공통적으로 카테시안 곱이 발생하여 중복이 생길 수 있다.
※ 카테시안 곱 : 두 테이블 사이에 유효 Join 조건을 적지 않았을 때 해당 테이블에 대한 모든 데이터를 전부 결합하여 테이블에 존재하는 행 갯수를 곱한만큼의 결과 값이 반환되는 것
해결방법?
1.
DISTINCT
•
query를 정의할 때 distinct 를 포함하여 중복을 제거하는 방법이다.
•
query 생성시 키워드만 같이 사용하면 되어 편리함
2.
LinkedHashSet
•
Entity의 OneToMany 관계를 가진곳의 타입을 Set으로 바꾸는 것이다.
•
Java의 set은 중복을 허용하지 않기 때문에, 중복된 데이터가 들어갈 일이 없어진다.
•
다만 set의 특징상 순서가 보장되지 않기 때문에 LinkedHashSet을 통해 중복제거 + 순서보장을 해주는 것이 좋다.