JPA N+1은 많이 겪어본 문제일듯한데.
쉬운 방법으로 해결하는 fetch join 방법을 소개한다.
우선 기본적으로 많이 쓰이는 3가지를 소개한다.
- @NamedEntityGraph 와 @EntityGraph 를 사용해 해결하기
- Querydsl 을 사용해 해결하기
- 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 커스터마이징) 도 참조 했다.
- 끗 -