코드를 보면서 N+1 문제와 이를 해결하기 위한 방안에 대해서 설명드리겠습니다.
@Test
public void findMemberLazy() throws Exception {
//given
//member1 -> teamA
//member2 -> teamB
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
teamRepository.save(teamA);
teamRepository.save(teamB);
memberRepository.save(new Member("member1", 10, teamA));
memberRepository.save(new Member("member2", 20, teamB));
em.flush();
em.clear();
//when
List<Member> members = memberRepository.findAll();
//then
for (Member member : members) {
member.getTeam().getName();
}
}
Member와 team은 지연로딩 관계입니다(N+1). 위 코드를 기반으로 member를 조회하는 경우에는 member에 관한 필드 값들은 DB에 있지만, team에 대한 값은 LAZY(지연로딩)로 설정되어있으면, 프록시라는 가짜 객체를 두게 됩니다. 그래서 member와 team 모두 지연로딩 관계이기 때문에 team에 대한 필드 값을 가져오는 경우 문제가 생깁니다.
member.getTeam().getName() 메서드에서 실행할 때, 팀에 관한 이름을 찾기 위해 실제 DB에서 쿼리 id를 바탕으로 데이터를 가져오게 됩니다. 그래서 쿼리문이 한 번 더 나가게 됩니다.
이러한 문제를 N+1 문제(쿼리를 1번 날렸는데, N+1번 나가게 되는 문제)라고 합니다.
전체 멤버를 조회하기 위해 쿼리 1번
팀 이름 조회 -> 쿼리 2번(팀 A, 팀 B)
해결 방안
1. Fetch Join 적용
Fetch Join을 적용하면, Member를 조회할 때 연관된 엔티티 Team 객체도 프록시 객체가 아닌, 실제 객체를 넣어둡니다. 그래서 불필요한 쿼리문을 날릴 필요가 없고, 한방쿼리로 N+1 문제를 해결할 수 있게 됩니다.
2. @EntityGraph
@EntityGraph(attributePaths = {"Member와 연관된 엔티티명"})를 걸어두면, Member를 조회할 때, Member와 연관된 엔티티인 Team에 대한 정보도 한방쿼리로 해결할 수 있다. 사실상 페치 조인이 내부적으로 쓰는 것이고, 페치 조인의 간편한 버전입니다.
LEFT OUTER JOIN을 사용합니다. 큰 장점은 JPQL을 작성하지 않고, 간편하게 연관된 데이터를 조회할 수 있다는 점입니다.
한방 쿼리 적용 후 쿼리문
다음과 같이 Fetch Join과 EntityGraph 어노테이션을 적용하여 다음과 같이 한방쿼리가 출력된 것을 볼 수 있게 됩니다.
N+1문제를 해결하기 위한 Fetch Join과 EntityGraph 어노테이션에 대해 알아보았습니다.
'Java > Spring' 카테고리의 다른 글
[Spring] LocalDateTime 형식의 데이터를 @JsonFormat, @DateTimeFormat어노테이션을 적용하여 데이터 주고받기 (0) | 2023.05.10 |
---|---|
[Spring] 이메일 인증을 구현하기 위한 설정 (SMTP, mail.properties) (0) | 2023.04.02 |
[Spring] Spring Data JPA Paging, Sort 기능 (0) | 2023.03.18 |
[Spring] Spring Data JPA 공통 인터페이스 (0) | 2023.03.18 |
[Spring] Querydsl (0) | 2023.03.12 |