본문 바로가기

SpringBoot

[JPA] @OneToOne 관계에서 EntityGraph 사용하기

JPA에서 OneToOne 맵핑 시 기본 Fetch TypeEager입니다. 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)),

쉿 코딩중([ Spring-Boot ] JPA @EntityGraph 사용하기 (tistory.com))

'SpringBoot' 카테고리의 다른 글

[JPA] EntityGraph를 더 잘 써보자  (0) 2023.01.19