JPQL: fetch join - 페치 조인, N+1의 문제

2022. 4. 4. 17:06JPA 기초

들어가기 앞서서 페이조인은..
• SQL 조인 종류가 아니다
• JPQL에서 성능 최적화를 위해 제공하는 기능이다.
• 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능이다
• join fetch 명령어로 사용하면 된다
• [ LEFT [OUTER] | INNER ] JOIN FETCH  조인 경로를 지정할 수 있다.

그럼 시작~!

다음과 같은 상황이 있다고 가정하다.
회원과 팀 엔티티가 있는 상황에서, 회원을 조회하면서 연관된 팀도 함께 조회하고 싶다. 회원도 궁금하고 그들의 소속 팀도 함께 보고싶다!!!!

이때 사용하는 것이 페치 조인이다.

select m from Member m join fetch m.team
select 프로젝션에 m 하나인데, 실제 SQL은 
select M.* T.* FROM MEMBER M INNER JOIN TEAM T on M.TEAM_ID= T.ID
로 나간다. 팀까지 싹쓸어서 가져온다

위 그림에서 Member와 Team을 INNER JOIN 실행시 회원 4는 조회되지 않는다.

영속성 컨텍스트는 1차캐시에 이 그림처럼 5개의 엔티티를 생성, 보관하고 있다가 반환해준다(쿼리는 한방에 회원, 팀을 조회하는 쿼리로 나간다).  

 


아니 그냥 일반 join 쓰거나 연관관계 fetch를 즉시(EAGER)로딩으로 바꾸면 되는거 아니야??  --> 아니다
다음과 같은 코드가 있다.

for (Member member : members) {
	 System.out.println("username = " + member.getUsername() + ", " + 
	 "teamName = " + member.getTeam().name()); 
}

페치 조인을 사용하면 회원과 팀을 한꺼번에 조회한다. 지연로딩이 발생하지 않는다. 깔끔하게 쿼리 한방으로 select M.*, T.* 가져오는 것을 확인했다.

String jpql = "select m from Member m join fetch m.team"; 
List<Member> members = em.createQuery(jpql, Member.class) 
 .getResultList();

멤버와 팀은 다대 일(ManyToOne)의 관계이며 fetch 타입은 LAZY(지연로딩)이라고 해보자.
이때  jpql을 날리면

em.createQuery("Select m From Member m" , Member.class)


List<Member>를 반환받을 수 있다. 여기까지는 좋다.  근데 이 코드에서 member.getTeam().name()을 만나는 순간
지연로딩이므로 천천히 기다리고 있던 프록시가 영속성컨텍스트에 초기화 요청을 보내고, 영속성 컨텍스트는 따라서 DB에 select쿼리를 날린다. 

for (Member member : members) {
	 System.out.println("username = " + member.getUsername() + ", " + 
	 "teamName = " + member.getTeam().name()); 
}

이때 N+1의 문제가 터져버린다. 만약 멤버의 팀이 모두 같다면 select team을 1회 호출한 뒤 영속성 컨텍스트에 1차캐시로 저장하므로 더이상의 쿼리문이 날아가지 않아서 괜찮은데,,
100명의 멤버가 있고 각각 다른 팀에 소속되어 있어 팀이 100개라면?
Team을 select하는 쿼리가 100개 날아간다.  멤버를 조회하는 쿼리 1개를 전송했는데 팀을 조회하는 쿼리 100개가 날아간다..


그렇다면 즉시(EAGER) 로딩은 괜찮을까?

1. 멤버 전체를 조회하기 위해 JPQL 실행한다. select m from member m
2. JPQL은 EAGER와 무관하게 SQL로 그대로 번역 ->  select m.* from member
3. JPQL 결과가 member만 조회하고, team은 조회하지 않는다. 하지만
4. member와 team이 즉시 로딩으로 설정되어 있기 때문에 연관된 팀을 각각 쿼리를 날려서 추가 조회 (N+1)

마찬가지로 N+1의 문제를 해결할 수 없다. N+1의 문제가 터지는 시점이 다를 뿐이다

오직 FETCH JOIN만이 해결 가능하다!!!!!!!!