JPA에서 OneToOne 맵핑 시 기본 Fetch Type은 Eager입니다. Eager는 연관관계가 있는 필드를 함께 조회합니다.
하지만 이러한 동작은 원치 않을 때도 실행되기 때문에 흔히 말하는 N+1 문제를 발생시킵니다. 정확하게 말하자면 FetchType을 LAZY로 설정하여도 테이블을 조회할 때 외래 키를 갖고 있는 테이블(연관 관계의 주인)에서는 지연로딩이 동작하지만, mappedBy로 연결된 반대편 테이블은 Eager로 동작하기 때문에 지연로딩이 동작하지 않고 N + 1 쿼리가 발생합니다.
N+1 문제란?
연관 관계에서 발생하는 이슈로 연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 개수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오게 된다. 이를 N+1 문제라고 한다.
예제코드
Member Entity
@Entity
@Setter
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "address_id")
private Address address;
}
Address Entity
@Entity
@Setter
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String location;
@OneToOne(fetch = FetchType.LAZY, mappedBy = "address")
private Member member;
}
member 마다 거주지 주소를 저장하는 address 를 가지는 구조입니다. member를 2명 만들고 그에 따른 address를 2개 만든 후 각각에 할당해 주었습니다. 그 후 List <address>를 가져오는 findAll() 메서드를 실행했을 때 발생한 쿼리입니다. fetchType을 지연로딩으로 설정해 주었지만 2개의 데이터를 가져오기 위해 3개의 쿼리가 발생했습니다. 이와 같은 상황이 N+1 문제입니다. 이를 해결하기 위해 FetchJoin 쿼리 작성 또는 EntityGraph를 사용합니다. 저는 EntityGraph를 도입해 보겠습니다.
=======================
address 목록 가져오기
=======================
Hibernate:
select
a1_0.id,
a1_0.location
from
address a1_0
Hibernate:
select
m1_0.id,
m1_0.address_id,
m1_0.name
from
member m1_0
where
m1_0.address_id=?
Hibernate:
select
m1_0.id,
m1_0.address_id,
m1_0.name
from
member m1_0
where
m1_0.address_id=?
EntityGraph
EntityGraph란?
연관관계가 있는 엔티티를 조회할 경우 지연 로딩으로 설정되어 있으면 연관관계에서 종속된 엔티티는
쿼리 실행 시 select 되지 않고 proxy 객체를 만들어 엔티티가 적용시킨다. 그 후 해당 proxy 객체를 호출할 때마다 그때그때 select 쿼리가 실행된다. 이 때 fetch 조인을 사용하여 여러 쿼리를 한 번에 해결할 수 있다.
EntityGraph는 이 때 어노테이션을 사용하여 간편하게 fetch 조인 쿼리를 만들어준다.
AddressRepository
public interface AddressRepository extends JpaRepository<Address, Long> {
@Override
@EntityGraph(attributePaths = {"member"})
List<Address> findAll();
}
기본적으로 jpa에서 제공하는 findAll()이라는 메서드가 존재하기 때문에 @Override를 통해 재정의 해줍니다.
@EntityGraph(attributePaths = {"member"})
어노테이션을 활용해 member를 함께 join해서 가져온다고 선언하여 주었습니다.
다음은 해당 어노테이션을 적용했을 때 발생한 쿼리이다. 쿼리를 1번 보내는 것으로 끝이 나버렸습니다. 복잡한 연관관계는 직접 fetch join 쿼리를 작성해야겠지만 이렇게 간단한 연관관계는 간단한 어노테이션을 활용하여 N+1 문제를 쉽게 해결할 수 있습니다.
=======================
address 목록 가져오기
=======================
Hibernate:
select
a1_0.id,
a1_0.location,
m1_0.id,
m1_0.address_id,
m1_0.name
from
address a1_0
left join
member m1_0
on a1_0.id=m1_0.address_id
출처 : Incheol's Tech Blog(N+1 문제 - Incheol's TECH BLOG (gitbook.io)),
'SpringBoot' 카테고리의 다른 글
| [JPA] EntityGraph를 더 잘 써보자 (0) | 2023.01.19 |
|---|