엔티티 그래프(Entity Graph)
JPA(EntityManager)나 Hibernate에서 제공하는 기능으로, 엔티티를 조회할 때 연관된 엔티티를 어떤 방식으로 가져올지 정의하는 방법이다. 이를 통해 명시적으로 패치 전략(Fetch Strategy)을 설정하고, 필요에 따라 지연 로딩(Lazy Loading)과 즉시 로딩(Eager Loading)을 제어할 수 있다.
엔티티 그래프를 사용하는가?
- N+1 문제 해결:
- JPA의 기본 설정에서 연관 엔티티는 지연 로딩(FetchType.LAZY)으로 설정한다.
- 필요 시 연관된 데이터를 쿼리하는데, 다수의 추가 쿼리가 발생해 성능 문제가 생길 수 있다.
- 이를 엔티티 그래프로 해결할 수 있다.
- JPQL 대체:
- 복잡한 JPQL을 사용하지 않고, 엔티티 그래프를 활용해 간결하고 명시적으로 원하는 연관 데이터를 가져올 수 있다.
- 선택적 로딩:
- 필요에 따라 연관된 특정 데이터만 로딩할 수 있어 성능 최적화가 가능하다.
예시 코드) member → team은 지연로딩 관계
@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);
Member member1 = new Member("member1", 10, teamA);
member1.changeTeam(teamA);
Member member2 = new Member("member2", 10, teamB);
member2.changeTeam(teamB);
memberRepository.save(member1);
memberRepository.save(member2);
em.flush();
em.clear();
// when N + 1
// select Member 1
List<Member> members = memberRepository.findAll();
for (Member member : members) {
System.out.println("member = " + member.getUsername());
System.out.println("member.teamClass = " + member.getTeam().getClass());
System.out.println("member.team = " + member.getTeam().getName());
}
}
- 따라서 다음과 같이 team의 데이터를 조회할 때 마다 쿼리가 실행된다.(N+1문제)
실행 결과
select
m1_0.member_id,
m1_0.age,
m1_0.team_id,
m1_0.username
from
member m1_0
member = member1
member.getTeam().getClass() = class study.data_jpa.entity.Team$HibernateProxy$yqMTZgrE
// 팀A가 필요한 시점에 팀 조회
select
t1_0.team_id,
t1_0.name
from
team t1_0
where
t1_0.team_id=?
member.getTeam().getName() = teamA
=========================================================================================
member = member2
member.getTeam().getClass() = class study.data_jpa.entity.Team$HibernateProxy$yqMTZgrE
// 팀B가 필요한 시점에 팀 조회
select
t1_0.team_id,
t1_0.name
from
team t1_0
where
t1_0.team_id=?
member.getTeam().getName() = teamB
- 만약 select Member(1)의 결과가 10개가 나왔다면 관련된 팀의 프록시를 초기화해야 하기 때문에 10(n)번의 쿼리가 추가로 나감.
참고: 다음과 같이 지연 로딩 여부를 확인할 수 있다.
//Hibernate 기능으로 확인 Hibernate.isInitialized(member.getTeam())
//JPA 표준 방법으로 확인
PersistenceUnitUtil util =
em.getEntityManagerFactory().getPersistenceUnitUtil();
util.isLoaded(member.getTeam());
JPQL 페치 조인
연관된 엔티티를 한번에 조회하려면 페치 조인이 필요하다.
@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();
- 스프링 데이터 JPA는 JPA가 제공하는 엔티티 그래프 기능을 편리하게 사용하게 도와준다.
- 이 기능을 사용하면 JPQL 없이 페치 조인을 사용할 수 있다. (JPQL + 엔티티 그래프도 가능)
실행 결과
select
m1_0.member_id,
m1_0.age,
t1_0.team_id,
t1_0.name,
m1_0.username
from
member m1_0
left join
team t1_0
on t1_0.team_id=m1_0.team_id
member = member1
member.teamClass = class study.data_jpa.entity.Team
member.team = teamA
member = member2
member.teamClass = class study.data_jpa.entity.Team
member.team = teamB
- 테스트는 위와 메서드 이름만 바뀌고 동일하다. (findAll() → findMemberFetchJoin())
- member.teamClass를 보면 프록시가 아니고 진짜 데이터가 다 채워진 진짜 엔티티가 나온것을 확인할 수 있다.
- 이를 통해, 페치 조인은 연관관계가 있는 객체를 데이터베이스의 조인을 활용해 한 번에 모두 조인하면서 select 절에 데이터들을 넣어주는 것을 확인할 수 있다.
그런데 매번 이렇게 Query로 JPQL을 적어줘야 할까?
- 예를 들면 findByUserName()처럼 메서드 이름으로 쿼리를 만들고 싶은데 페치 조인까지 하고 싶은 경우가 있을 것이다.
- Spring Data JPA 는 엔티티 그래프로 이런 문제를 해결해준다.
엔티티 그래프
//공통 메서드 오버라이드
@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는 JPA가 제공하는 엔티티 그래프 기능을 편리하게 사용하게 도와준다.
- 이 기능을 사용하면 JPQL 없이 페치 조인을 사용할 수 있다. (JPQL + 엔티티 그래프도 가능)
-
사실상 페치 조인(FETCH JOIN)의 간편 버전
-
LEFT OUTER JOIN 사용
NamedEntityGraph 사용 방법
@NamedEntityGraph(name = "Member.all", attributeNodes =
@NamedAttributeNode("team"))
@Entity
public class Member {...}
@EntityGraph("Member.all")
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
- 엔티티 그래프를 또 다른 방법으로 사용할 수 있다.
- @NamedQuery와 사용 방법이 비슷하다.
'JPA' 카테고리의 다른 글
[Spring Data JPA] 사용자 정의 리포지토리 구현 (0) | 2025.01.18 |
---|---|
[Spring Data JPA] JPA Hint & Lock (0) | 2025.01.18 |
[Spring Data JPA] 벌크성 수정 쿼리 (0) | 2025.01.17 |
[Spring Data JPA] 페이징과 정렬 (0) | 2025.01.16 |
[JPA] OSIV와 성능 최적화 (0) | 2025.01.10 |