Ollsy 프로젝트의 성능을 개선하기 위해 쿼리 발생 개수를 개선해 보겠다. + JPA 연관관계 복습
확실한 비교를 위해 Item의 데이터를 10만개 만들었다.
public class Item extends DateEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "item_id")
Long id;
@Column(nullable = false)
String name;
@Column(nullable = false)
String description;
@Column(nullable = false)
int price;
@Column(nullable = false)
int stock;
@ManyToOne
@JoinColumn(name = "category_id")
private Category category;
@OneToMany(mappedBy = "item", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ItemImage> images = new ArrayList<>();
Itme 클래스는 ItemImage, Category클래스와 연관관계를 맺고 있음
Category클래스와는 연관관계의 주인을 맡고 있고 ItemImage와는 양방향 연관관계를 맺고 있다.
@Test
@DisplayName("N+1 발생 테스트")
void nPlusOneTest() {
System.out.println("----------- Item 전체 조회 -----------");
List<Item> items = itemRepository.findAll();
System.out.println("----------- Item 전체 조회 완료[쿼리 1개 발생하남??] -----------");
}
이렇게 테스트 코드를 작성해 보았다. Item을 가져오는 것이었고 당연히 Item만 가져오니 쿼리는 select쿼리 하나만 가져오겠지?
결과
----------- Item 전체 조회 -----------
[Hibernate]
/* <criteria> */ select
i1_0.item_id,
i1_0.category_id,
i1_0.create_at,
i1_0.description,
i1_0.name,
i1_0.price,
i1_0.stock,
i1_0.update_at
from
items i1_0
[Hibernate]
select
c1_0.category_id,
c1_0.depth,
c1_0.name,
c1_0.parent_id
from
categories c1_0
where
c1_0.category_id=?
[Hibernate]
select
c1_0.category_id,
c1_0.depth,
c1_0.name,
c1_0.parent_id
from
categories c1_0
where
c1_0.category_id=?
[Hibernate]
select
c1_0.category_id,
c1_0.depth,
c1_0.name,
c1_0.parent_id
from
categories c1_0
where
c1_0.category_id=?
[Hibernate]
select
c1_0.category_id,
c1_0.depth,
c1_0.name,
c1_0.parent_id
from
categories c1_0
where
c1_0.category_id=?
----------- Item 전체 조회 완료[쿼리 1개 발생하남??] -----------
5개의 select 쿼리가 발생했다. 왜? → 기본적으로 ManyToOne의 관계는 Eager조회를 기본으로 하고 있기 때문이다. Eager조회는 해당 엔티티를 조회할 때 엔티티가 연관관계 주인으로 있는 엔티티를 전체 조회하기 때문이다. 즉 item을 조회할 때 마다 Category를 전부 조회하기 때문이다. N+1 발생!!
방법: Fetch전략 lazy로 변경
코드의 Fetch전략을 lazy로 변경해 주었다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private Category category;
----------- Item 전체 조회 -----------
[Hibernate]
/* <criteria> */ select
i1_0.item_id,
i1_0.category_id,
i1_0.create_at,
i1_0.description,
i1_0.name,
i1_0.price,
i1_0.stock,
i1_0.update_at
from
items i1_0
----------- Item 전체 조회 완료[쿼리 1개 발생하남??] -----------
보시는 것과 같이 item을 가져오는 쿼리 하나만 발생했다. → Lazy는 연관관계에 있는 엔티티를 직접 조회하는 것이 아니면 별도의 쿼리가 발생하지 않는다. 즉 연관된 엔티티(Category)를 필요할 때 까지 쿼리 발생 시점을 늦추는 것이지 N+1이 발생하지 않는 것이 아니다.