JPA N+1은 많이 겪어본 문제일듯한데.

쉬운 방법으로 해결하는 fetch join 방법을 소개한다.

우선 기본적으로 많이 쓰이는 3가지를 소개한다.

  1. @NamedEntityGraph 와 @EntityGraph 를 사용해 해결하기
  2. Querydsl 을 사용해 해결하기
  3. JPQL 을 사용해 해결하기

나는 여기 중에서 1번과 비슷하게 사용하는 방법을 소개한다.

코드를 보자

우선 Entity 클래스들부터 보자 Company와 Product가 있다.

@Entity
public class Company {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "company", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
    private List<Product> products = new ArrayList<>();

    @Builder
    public Company(Long id, String name, List<Product> products) {
        this.id = id;
        this.name = name;
        this.products = products;
    }

    public void setProducts(List<Product> products) {
        this.products = products;
    }

}

@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "company_id")
    private Company company;

    @Builder
    public Product(Long id, String name, Company company) {
        this.id = id;
        this.name = name;
        this.company = company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }

}

다음으로 Repository를 보자

@Repository
public interface CompanyRepository extends JpaRepository<Company, Long> {

    @Override
    List<Company> findAll();
    
}

데이터는 다음과 같다.

Company Table

id Name
7 Coke
8 Nike

Product Table

id Name company_id
9 CocaCola 7
10 Sprite 7
11 ZeroCola 7
12 Shoes 8

우선 이렇게 돼있는 코드를 테스트 돌려보자.

@SpringBootTest
public class JpaNPlusOneTest {
    
    @Autowired
    CompanyRepository companyRepository;
    
    @Test
    void findCompanyTest() {
        companyRepository.findAll();
    }

}
Hibernate: 
    select
        company0_.id as id1_0_,
        company0_.name as name2_0_ 
    from
        company company0_
Hibernate: 
    select
        products0_.company_id as company_3_1_0_,
        products0_.id as id1_1_0_,
        products0_.id as id1_1_1_,
        products0_.company_id as company_3_1_1_,
        products0_.name as name2_1_1_ 
    from
        product products0_ 
    where
        products0_.company_id=?
Hibernate: 
    select
        products0_.company_id as company_3_1_0_,
        products0_.id as id1_1_0_,
        products0_.id as id1_1_1_,
        products0_.company_id as company_3_1_1_,
        products0_.name as name2_1_1_ 
    from
        product products0_ 
    where
        products0_.company_id=?

결과를 보게 되면 company를 찾고 찾은 company를 1개씩 product에서 검색한다.

그럼 이제 여기서 Repository 위에 이번에 글에서 말하고 싶은 @EntityGraph(attributePaths = {"products"}, type = EntityGraph.EntityGraphType.LOAD) 어노테이션을 붙이고 실행해 보자

@Repository
public interface CompanyRepository extends JpaRepository<Company, Long> {

    @EntityGraph(attributePaths = {"products"}, type = EntityGraph.EntityGraphType.LOAD)
    @Override
    List<Company> findAll();
    
}
Hibernate: 
    select
        company0_.id as id1_0_0_,
        products1_.id as id1_1_1_,
        company0_.name as name2_0_0_,
        products1_.company_id as company_3_1_1_,
        products1_.name as name2_1_1_,
        products1_.company_id as company_3_1_0__,
        products1_.id as id1_1_0__ 
    from
        company company0_ 
    left outer join
        product products1_ 
            on company0_.id=products1_.company_id

결과를 보면 3번의 조회에서 1번의 조회로 해결된 것을 볼 수가 있다.

글 위에서 소개한 1번의 내용 중 @NamedEntityGraph를 사용하지 않고 @EntityGraph만으로도 사용이 가능하다.

더 간결해 편해질 수 있다.

참고로 @EntityGraph에 있는 type으로는 2가지가 있는데

Enum Constant Detail

LOAD

EntityGraph.EntityGraphType LOAD

javax.persistence.loadgraph 속성을 사용하여 엔티티 그래프를 지정할 때 엔티티 그래프의 속성 노드에 의해 지정된 속성은 FetchType.EAGER로 처리되고 지정되지 않은 속성은 지정된 또는 기본 FetchType에 따라 처리됩니다.

FETCH

EntityGraph.EntityGraphType FETCH

javax.persistence.fetchgraph 속성을 사용하여 엔티티 그래프를 지정할 때 엔티티 그래프의 속성 노드에 의해 지정된 속성은 FetchType.EAGER로 처리되고 지정되지 않은 속성은 FetchType.LAZY로 처리됩니다.

라고 Enum EntityGraph.EntityGraphType 여기에 되있다

이글 [Spring Data] @EntityGraph로 lazy 패치 같이 불러오기 (fetch-graph 커스터마이징) 도 참조 했다.

- 끗 -

image