실무에서는 부가적으로 가져올 데이터들이 많아서
N+1 문제
많이 발생함 ⇒fetch join
으로 해결
@Test
public void findMemberLazy(){
// given
// member1 -> teamA
// member2 -> teamB
// 관계 : Member - Team : @ManyToOne, LAZY
// FetchType = LAZY >> Member 조회 시, Team은 "프록시 객체"로 조회. 실제 Team 사용시 실제 쿼리 날라가면서 Team 객체 초기화됨.
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
teamRepository.save(teamA);
teamRepository.save(teamB);
Member member1 = new Member("member1", 10, teamA);
Member member2 = new Member("member2", 10, teamB);
memberRepository.save(member1);
memberRepository.save(member2);
em.flush();
em.clear();
// when
List<Member> members = memberRepository.findAll(); // 쿼리 수: 1
for (Member member : members) {
System.out.println("member = " + member.getUsername());
System.out.println("member.teamClass = " + member.getTeam().getClass()); // team 프록시 객체 확인
// team 객체 프록시 초기화
System.out.println("member.team = " + member.getTeam().getName()); // 쿼리 수: member 수만큼
}
}
모든 Member 가져오기 위한 쿼리 (쿼리수 : 1개
- 이때는 Team은 프록시 객체)
+
각 Member의 Team이 모두 다르다면, Member 1명 당 Team 프록시 객체 초기화를 하기 위한 쿼리 (쿼리수 : N개
)
Fetch Join
: Member와 연관된 Team 모두 Select 절에서 조회해 한방 쿼리
로 가져온다.
fetch join
// Fetch Join
// member 조회시 연관된 team을 같이 "한 방 쿼리"로 가져옴
@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();
실행된 결과를 보면, select 절
에 Member의 필드 값 뿐만 아니라, Team의 필드 값까지 조회
해오는 것을 볼 수 있다.
한방 쿼리로 모든 데이터를 다 끌고 오기 때문에 Member엔티티의 Team 객체에 실제 Team 객체를 생성해서 넣어둔다.
즉, 객체 그래프 탐색을 하면 member.getTeam()에 실제 객체가 들어있다.
Spring Data JPA에서는 @EntityGraph
를 사용해서 메서드 이름으로 쿼리 생성 + fetch join
해결
=> 물론 @Query("fetch join JPQL 작성")해서 해도 되지만! 귀찮다!
@EntityGraph(attributePaths = {"fetch join 할 객체의 필드명"}
// Entity Graph
@Override
@EntityGraph(attributePaths = {"team"}) // 객체의 필드명
List<Member> findAll();
// JPQL에 Entity Graph추가(fetch join)
@EntityGraph(attributePaths = {"team"})
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
// 메서드 이름으로 쿼리 생성 + EntityGraph추가(fetch join)
@EntityGraph(attributePaths = {"team"})
List<Member> findEntityGraphByUsername(@Param("username") String username);
위의 결과에서 맨 끝에 where 절에서 username 확인하는 문장만 추가되었을 뿐이다!
@EntityGraph
를 사용한 결과 fetch join
이 적용되었음을 확인 할 수 있다.
거의 사용하지 않지만, 그래도 JPA 표준 스펙에 있으니, 설명만!
Member Entity 위에
@NamedEntityGraph(name = "엔티티 그래프 이름 정의", attributeNodes = @NamedAttributeNode(fetch join 할 대상))
@NamedEntityGraph(name = "Member.all", attributeNodes = @NamedAttributeNode("team"))
public class Member{
...
}
@EntityGraph(정의한 엔티티 그래프 이름)
// Member 객체에 선언한 @NamedEntityGraph를 사용
@EntityGraph("Member.all")
List<Member> findEntityGraphByUsername(@Param("username") String username);
간단 :
@EntityGraph
복잡 :JPQL에 fetch join
직접 작성