JPA의 Fetch Join 이해
1. EntityGraph를 이해하기 위해서는 JPA의 fetch join에 대해서 이해해야 한다.
2. fetch join에 대해서 이해하려면 N + 1 문제를 이해해야 하고,
3. N + 1 문제를 이해하기 위해선 FetchType.(Lazy와 Eager)와 Proxy 개념에 대한 이해가 필요하다.
차근차근 가보자
FetchType.Lazy와 FetchType.EAGER
JPA에서 연관관계 매핑을 할 때 @xxxToMany와 @xxxToOne으로 나뉜다.
여기서 xxxToMany는 기본적으로 FetchType이 Lazy이다. xxxToMany는 FetchType이 기본적으로 EAGER이다.
FetchType은 EAGER와 LAZY로 나뉜다.
EAGER Loading
EAGER는 본인과 관련된 모든 엔티티를 조회하는 것이다. 어릴 적 문제를 풀 때 '모두' '전체' '전혀'라는 말이 있으면 직감적으로 이상한 것이 있는지 의심했던 것처럼 이도 사용하면 안 된다.
그러한 이유는
1. 어떠한 Query가 나갈지 예상할 수 없다.
2. 성능 문제가 우려된다.
그렇기 때문에 무조건! FetchType은 Lazy로 두고 사용해야 한다.
Lazy Loading
한글로는 '지연로딩'이라고 표현하며, 가짜 프록시 객체를 사용하는 방식이다.
예를 들어 Member 엔티티가 Team과 연관관계 매핑을 갖고 있다면, Member를 조회할 때 Team은 가짜 프록시 객체이다.
member.getTeam()을 호출할 때까지는 프록시 상태를 유지하고, 실제로 member.getTeam().getName()과 같은 방식으로 데이터에 접근할 때 데이터베이스 쿼리가 발생한다. 즉, 실제 사용 시점에 데이터베이스 쿼리가 이루어진다.
가짜 프록시 객체로 두다가 실제로 사용할 때 데이터를 꺼낸 Query가 나간다.
이제 N + 1 문제에 대해 학습할 준비가 되었다.
N + 1문제 이해
쉽게 이해하기 위해 예시를 들어보자.
member1은 teamA이고 member2는 teamB라고 해보자.
여기서 모든 member와 그에 해당하는 team 이름을 조회할 때
지연로딩을 사용하게 되면, member1을 찾는 전체 쿼리 1개, member1의 팀 이름을 찾는 쿼리 1개, member2의 팀 이름을 찾는 쿼리 1개 총 3개가 나가게 된다.
우리는 member의 이름과 team의 이름을 찾기를 원했는데 총 3개의 쿼리가 나가게 된 것이다.
'우리는 member의 이름과 team의 이름을 찾기를 원했는데' 까지가 1이고, 'member가 1, 2이면서 Team이 다르므로 2개' 이것이 N이다. 그래서 1 + N을 N + 1 문제라고 한다.
그렇다면 이를 어떻게 해결해야 하나?
Fetch join 해결법
JPA의 fetch join이 존재한다. 이는 조회를 할 때, 조인하는 엔티티를 같은 쿼리로 해결하는 방법이다.
fetch join이란 member와 team을 join 해서 가져올 때 한 번에 갖고 오는 것이다.
필자는 처음에 fetch join을 쓰지 말고 EAGER를 쓰면 되는 것 아닌가? 헷갈렸다.
하지만 둘의 가장 큰 차이는, 한 번에 가져올 때 EAGER는 본인과 관련된 엔티티 전부를 갖고 오는 것이고, fetch join은 그 범위를 설정할 수가 있는 차이가 있다.
fetch join으로 갖고 오게 된다면, 하나의 쿼리로 이를 해결할 수 있다.
사실 이전 ORM을 학습할 때 이를 더 자세히 기록해 놓았다.
2023.10.04 - [JPA/ORM 표준 JPA 기본] - [ORM 표준 JPA] 10. 프록시와 N + 1 문제 FetchType.LAZY
순수 JPA의 fetch join
em.createQuery("select m From Member m join fetch Team", Member.class)
.getResultList();
Spring Data JPA
@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();
여기서 문제는, Spring Data JPA는 자동화의 매력의 끝인데 과연 이렇게 직접 적어줘야 하는 걸까?
여기서 EntityGraph가 이를 해결해 준다.
@EntityGraph의 활용
@EntityGraph는 JPA에서 fetch join을 보다 편리하게 사용할 수 있게 해주는 어노테이션이며, 엔티티의 특정 속성을 fetch join으로 가져오는 것을 간편하게 설정할 수 있다.
이를 활용하여 성능 최적화를 할 수 있으며, N + 1 문제를 해결하는 데에도 유용하다.
@Override
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();
이 메서드는 Member 엔티티를 조회할 때, 연관된 team 엔티티도 함께 조회한다.
attributePaths 속성을 통해 fetch join 할 속성을 지정한다.
Query와 결합
@EntityGraph를 JPQL(Query)과 결합하여 사용할 수도 있다. 아래는 @Query와 @EntityGraph를 함께 사용하는 예시이다.
// Query를 직접 짜는데 Fetch join부분만 자동화
@EntityGraph
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
둘 중 어떤 것이던 team과 함께 fetch join 하는 것과 같다.
QueryMethod와 조합
@EntityGraph는 Spring Data JPA의 QueryMethod와도 조합하여 사용할 수 있다.
특정 조건에 따라 fetch join을 적용하는 예시는 다음과 같다.
// 특정 조건에서 fetch join
@EntityGraph(attributePaths = {"team"})
List<Member> findByUsername(String username);
QueryMethod가 아닌 특정 조건에서도 위와 같이 @Param만 넘겨주면 사용할 수 있다.
'JPA > Spring Data JPA' 카테고리의 다른 글
[Spring Data JPA] 6. 사용자 정의 Repository. DI 활용 (0) | 2023.11.24 |
---|---|
[Spring Data JPA] 5. JPA Hint & Lock (0) | 2023.11.23 |
[Spring Data JPA] 3. 벌크성 수정 쿼리 (0) | 2023.11.23 |
[Spring Data JPA] 2. Spring Data JPA Paging (0) | 2023.11.23 |
[Spring Data JPA] 1. QueryMethod (0) | 2023.11.21 |