스프링 JPA에서 특정 엔티티를 조회할 때, ManytoOne관계(1대다)일 경우 FetchType.LAZY 사용을 권장한다.
하지만 이럴경우, SQL을 호출 할 때 N+1 문제가 발생하게 된다.
📕 fetch join
📗 엔티티 구조와 N+1문제
[Member]
@Entity
@Data
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
private int age;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
}
[Team]
@Entity
@Getter
@Setter
public class Team {
@Id @GeneratedValue
@Column(name = "team_id")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
Member -> team은 일대다의 지연로딩 관계를 가지고 있어, Member엔티티의 team의 데이터를 조회할 때 마다 쿼리가 실행된다.
이것은 N+1 문제 라고한다.
@Test
public void findMemberLazy() {
//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", 15, teamB));
em.flush();
em.clear();
//when
List<Member> members = memberRepository.findAll();
for (Member member : members) {
System.out.println("member = " + member.getUsername());
System.out.println("teamClass = " + member.getTeam().getClass());
System.out.println("team = " + member.getTeam());
}
}
📗 fetch join
이렇게 연관된 엔티티를 한번에 조회하는 기능으로 fetch join이 있다.
@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();
하지만... 필요할 때마다 fetch join을 작성하는 것은 비효율적이다.
스프링 데이터 JPA는 JPA가 제공하는 엔티티 그래프 기능을 편리하게 사용하게 도와준다.
이 기능을 사용하면 JPQL 없이 페치 조인을 사용할 수 있다.
📕 EntityGraph
📗 EntityGraph
//공통 메서드 오버라이드
@Override
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();
//JPQL + 엔티티 그래프
@EntityGraph(attributePaths = {"team"})
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
//메서드 이름으로 쿼리에서 특히 편리하다.
@EntityGraph(attributePaths = {"team"})
List<Member> findByUsername(String username)
사용방법은 쉽다.
그냥 평소 JPA작성하던 거에 @EntityGraph(attributePaths= {}) 를 추가한다.
📗 NamedEntityGraph
[Member]
@NamedEntityGraph(name = "Member.all", attributeNodes =
@NamedAttributeNode("team"))
@Entity
public class Member {}
[MemberRepository]
@EntityGraph("Member.all")
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
Member 엔티티에 이름을 지정해서 사용하는 것도 가능하다.
📕 프로젝트 중 활용
- Member와 Characters는 1대다 관계
- 초기 코드는 username으로 Member엔티티를 찾고 그 엔티티로 Characters 불렀음
- 총 2번 SQL을 호출
- fetch join으로 한번에 해결
@Query(value = "SELECT DISTINCT m FROM Member m JOIN FETCH m.characters c WHERE c.selected = true ORDER BY c.itemLevel DESC")
Member findByUsernameSelected(String username);