11. @EntityGraph

민정·2022년 12월 12일
0

Spring Data JPA

목록 보기
11/17
post-thumbnail

Fetch Join

실무에서는 부가적으로 가져올 데이터들이 많아서 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 수만큼
        }
    }

🔴 N+1 문제 발생

모든 Member 가져오기 위한 쿼리 (쿼리수 : 1개 - 이때는 Team은 프록시 객체)
+
각 Member의 Team이 모두 다르다면, Member 1명 당 Team 프록시 객체 초기화를 하기 위한 쿼리 (쿼리수 : N개)

🟢 N+1 문제 해결 : fetch Join

Fetch Join : Member와 연관된 Team 모두 Select 절에서 조회해 한방 쿼리로 가져온다.

📁 MemberRepository

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()에 실제 객체가 들어있다.



@EntityGraph

Spring Data JPA에서는 @EntityGraph를 사용해서 메서드 이름으로 쿼리 생성 + fetch join 해결
=> 물론 @Query("fetch join JPQL 작성")해서 해도 되지만! 귀찮다!

사용법

@EntityGraph(attributePaths = {"fetch join 할 객체의 필드명"}

📁 MemberRepository

    // 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);

findAll, findMemberEntityGraph의 결과 생성된 SQL

findEntityGraphByUsername의 결과 생성된 SQL

위의 결과에서 맨 끝에 where 절에서 username 확인하는 문장만 추가되었을 뿐이다!

@EntityGraph를 사용한 결과 fetch join이 적용되었음을 확인 할 수 있다.



참고! @NamedEntityGraph + @EntityGraph

거의 사용하지 않지만, 그래도 JPA 표준 스펙에 있으니, 설명만!

⭐ Member Entity

Member Entity 위에
@NamedEntityGraph(name = "엔티티 그래프 이름 정의", attributeNodes = @NamedAttributeNode(fetch join 할 대상))

@NamedEntityGraph(name = "Member.all", attributeNodes = @NamedAttributeNode("team"))
public class Member{
	...
}

📁 MemberRepository

@EntityGraph(정의한 엔티티 그래프 이름)

    // Member 객체에 선언한 @NamedEntityGraph를 사용
    @EntityGraph("Member.all")
    List<Member> findEntityGraphByUsername(@Param("username") String username);


📌 정리

간단 : @EntityGraph
복잡 : JPQL에 fetch join 직접 작성


출처

김영한 강사님 - 인프런 실전! 스프링 데이터 JPA

0개의 댓글