JPA를 사용하다보면 예상보다 DB 쿼리가 더 많이 나가서 성능에 문제가 생기는 경우가 있다. 이 경우 대부분 개발자도 모르게 N+1문제가 기본적으로 깔려있는 경우가 많다. 그렇다면 왜 이러한 문제가 발생하고 어떻게하면 해결할 수 있는 지 자세하게 알아보자.

간단하게 위와 같은 연관 관계가 있다.
Member 엔티티를 조회하는 상황이 이라고 가정할때, 즉시 로딩의 경우 Member뿐만 아니라 Team의 속성도 모두 조회해서 가져온다.
그렇다면 실제 SQL 쿼리는 Member를 조회하는 쿼리 1개 Team을 조회하는 쿼리 1개가 나가서 총 2개의 쿼리가 나가게 된다.
//멤버를 조회하는 쿼리
select
member0_.id as id1_0_,
member0_.team_id as team_id3_0_,
member0_.username as username2_0_
from
Member member0_
//팀을 조회하는 쿼리
select
team0_.id as id1_3_0_,
team0_.name as name2_3_0_
from
Team team0_
where
team0_.id=?
즉, 개발자는 Member만 조회하려고 하는데 한번엔 Team도 다 같이 가져오는 것이다. 물론 Team까지 한번에 조회하는게 유리한 상황이 있다. 하지만 엔티티에 이러한 설정이 되어있을 경우 그러한 상황이 아닌데도 쓸데없이 쿼리가 나가는 것이다.
만약 관계가 여러개 얽혀있다면 그 관계의 수만큼 추가적인 쿼리가 나갈 것이다. (그래서 사실 N+1문제는 1+N문제라고 하는게 맞는거 같다) 그렇다면 그만큼 성능이 떨어지게 될 것이다!
지연 로딩의 경우 Member를 조회하는 시점에 Member의 속성만을 쿼리한다. 그리고 .getTeam().getName()같이 실제로 Team속성을 사용하는 시점에 Team을 조회하는 쿼리가 나간다.
select
member0_.id as id1_0_,
member0_.team_id as team_id3_0_,
member0_.username as username2_0_
from
Member member0_
정리하자면 기본적으로는 Member만을 조회하고 필요할 때 연관 관계 쿼리가 나가는 것이다. 이 방법을 사용하면 즉시 로딩에서 발생하는 문제를 간단하게 해결할 수 있다.
위에서 나왔지만 그냥 LAZY 로딩만 쓰면 N+1문제를 해결할 수 있다. 그리고 Team까지 한꺼번에 조회하는게 성능상 더 유리한 경우에는 fetch join을 통해 해결하면 된다!
소름 돋게도 @XXToOne 연관 관계 매핑 어노테이션은 디폴트가 EAGER로 설정 되어있다(@XXToMany 디폴트는 LAZY). 그래서 @XXToOne를 사용할 때 @ManyToOne(fetch = FetchType.LAZY) 를 설정하기만 하면 된다.
하지만 지연 로딩을 써도 N+1문제는 완벽하게 해결되지 않는다. 어쨋든 사용 시에 쿼리가 나가기 때문에 N번 더 쿼리가 나가기 때문이다. 다음 N+1문제 2편에서는 fetch join을 사용해서 이 문제에 대해 자세하게 알아보자