Hibernate:
select
p1_0.product_id,
p1_0.authority_status,
p1_0.average_rating,
p1_0.brand_id,
p1_0.created_at,
p1_0.deleted_at,
p1_0.delivery_fee,
p1_0.product_description,
p1_0.like_count,
p1_0.modified_at,
p1_0.product_name,
p1_0.seller_id,
p1_0.status,
p1_0.subcategory_id
from
product p1_0
where
lower(p1_0.product_name) like ? escape '!'
Hibernate:
select
b1_0.brand_id,
b1_0.authority_status,
b1_0.created_at,
b1_0.deleted_at,
b1_0.description,
b1_0.like_count,
b1_0.modified_at,
b1_0.name,
s1_0.seller_id,
s1_0.cp_address,
s1_0.cp_code,
s1_0.cp_email,
s1_0.cp_name,
s1_0.cp_password,
s1_0.created_at,
s1_0.deleted_at,
s1_0.ep_name,
s1_0.modified_at,
s1_0.net_proceeds,
s1_0.phone_number,
s1_0.role,
s1_0.status,
b1_0.status
from
brand b1_0
left join
seller s1_0
on s1_0.seller_id=b1_0.seller_id
where
b1_0.brand_id=?
Hibernate:
select
s1_0.seller_id,
b1_0.brand_id,
b1_0.authority_status,
b1_0.created_at,
b1_0.deleted_at,
b1_0.description,
b1_0.like_count,
b1_0.modified_at,
b1_0.name,
b1_0.status,
s1_0.cp_address,
s1_0.cp_code,
s1_0.cp_email,
s1_0.cp_name,
s1_0.cp_password,
s1_0.created_at,
s1_0.deleted_at,
s1_0.ep_name,
s1_0.modified_at,
s1_0.net_proceeds,
s1_0.phone_number,
s1_0.role,
s1_0.status
from
seller s1_0
left join
brand b1_0
on s1_0.seller_id=b1_0.seller_id
where
s1_0.seller_id=?
Hibernate:
select
sc1_0.sub_category_id,
sc1_0.created_at,
sc1_0.deleted_at,
sc1_0.main_category_id,
mc1_0.main_category_id,
mc1_0.created_at,
mc1_0.deleted_at,
mc1_0.modified_at,
mc1_0.name,
mc1_0.status,
sc1_0.modified_at,
sc1_0.name,
sc1_0.status
from
sub_category sc1_0
join
main_category mc1_0
on mc1_0.main_category_id=sc1_0.main_category_id
where
sc1_0.sub_category_id=?
Hibernate:
select
b1_0.brand_id,
b1_0.authority_status,
b1_0.created_at,
b1_0.deleted_at,
b1_0.description,
b1_0.like_count,
b1_0.modified_at,
b1_0.name,
s1_0.seller_id,
s1_0.cp_address,
s1_0.cp_code,
s1_0.cp_email,
s1_0.cp_name,
s1_0.cp_password,
s1_0.created_at,
s1_0.deleted_at,
s1_0.ep_name,
s1_0.modified_at,
s1_0.net_proceeds,
s1_0.phone_number,
s1_0.role,
s1_0.status,
b1_0.status
from
brand b1_0
left join
seller s1_0
on s1_0.seller_id=b1_0.seller_id
where
b1_0.brand_id=?
Hibernate:
select
s1_0.seller_id,
b1_0.brand_id,
b1_0.authority_status,
b1_0.created_at,
b1_0.deleted_at,
b1_0.description,
b1_0.like_count,
b1_0.modified_at,
b1_0.name,
b1_0.status,
s1_0.cp_address,
s1_0.cp_code,
s1_0.cp_email,
s1_0.cp_name,
s1_0.cp_password,
s1_0.created_at,
s1_0.deleted_at,
s1_0.ep_name,
s1_0.modified_at,
s1_0.net_proceeds,
s1_0.phone_number,
s1_0.role,
s1_0.status
from
seller s1_0
left join
brand b1_0
on s1_0.seller_id=b1_0.seller_id
where
s1_0.seller_id=?
Hibernate:
select
sc1_0.sub_category_id,
sc1_0.created_at,
sc1_0.deleted_at,
sc1_0.main_category_id,
mc1_0.main_category_id,
mc1_0.created_at,
mc1_0.deleted_at,
mc1_0.modified_at,
mc1_0.name,
mc1_0.status,
sc1_0.modified_at,
sc1_0.name,
sc1_0.status
from
sub_category sc1_0
join
main_category mc1_0
on mc1_0.main_category_id=sc1_0.main_category_id
where
sc1_0.sub_category_id=?
1. 상황
SubCategory(하위 카테고리),ProductOption(상품 옵션),Seller(판매자),ProductImage(상품 이미지),Brand(브랜드)가 있다.Brand/Seller/SubCategory에 대한 조회를2번씩하여총 1+6번의 SELECT 쿼리가 실행되었다.Hibernate: select p1_0.product_id, p1_0.authority_status, p1_0.average_rating, p1_0.brand_id, p1_0.created_at, p1_0.deleted_at, p1_0.delivery_fee, p1_0.product_description, p1_0.like_count, p1_0.modified_at, p1_0.product_name, p1_0.seller_id, p1_0.status, p1_0.subcategory_id from product p1_0 where lower(p1_0.product_name) like ? escape '!' Hibernate: select b1_0.brand_id, b1_0.authority_status, b1_0.created_at, b1_0.deleted_at, b1_0.description, b1_0.like_count, b1_0.modified_at, b1_0.name, s1_0.seller_id, s1_0.cp_address, s1_0.cp_code, s1_0.cp_email, s1_0.cp_name, s1_0.cp_password, s1_0.created_at, s1_0.deleted_at, s1_0.ep_name, s1_0.modified_at, s1_0.net_proceeds, s1_0.phone_number, s1_0.role, s1_0.status, b1_0.status from brand b1_0 left join seller s1_0 on s1_0.seller_id=b1_0.seller_id where b1_0.brand_id=? Hibernate: select s1_0.seller_id, b1_0.brand_id, b1_0.authority_status, b1_0.created_at, b1_0.deleted_at, b1_0.description, b1_0.like_count, b1_0.modified_at, b1_0.name, b1_0.status, s1_0.cp_address, s1_0.cp_code, s1_0.cp_email, s1_0.cp_name, s1_0.cp_password, s1_0.created_at, s1_0.deleted_at, s1_0.ep_name, s1_0.modified_at, s1_0.net_proceeds, s1_0.phone_number, s1_0.role, s1_0.status from seller s1_0 left join brand b1_0 on s1_0.seller_id=b1_0.seller_id where s1_0.seller_id=? Hibernate: select sc1_0.sub_category_id, sc1_0.created_at, sc1_0.deleted_at, sc1_0.main_category_id, mc1_0.main_category_id, mc1_0.created_at, mc1_0.deleted_at, mc1_0.modified_at, mc1_0.name, mc1_0.status, sc1_0.modified_at, sc1_0.name, sc1_0.status from sub_category sc1_0 join main_category mc1_0 on mc1_0.main_category_id=sc1_0.main_category_id where sc1_0.sub_category_id=? Hibernate: select b1_0.brand_id, b1_0.authority_status, b1_0.created_at, b1_0.deleted_at, b1_0.description, b1_0.like_count, b1_0.modified_at, b1_0.name, s1_0.seller_id, s1_0.cp_address, s1_0.cp_code, s1_0.cp_email, s1_0.cp_name, s1_0.cp_password, s1_0.created_at, s1_0.deleted_at, s1_0.ep_name, s1_0.modified_at, s1_0.net_proceeds, s1_0.phone_number, s1_0.role, s1_0.status, b1_0.status from brand b1_0 left join seller s1_0 on s1_0.seller_id=b1_0.seller_id where b1_0.brand_id=? Hibernate: select s1_0.seller_id, b1_0.brand_id, b1_0.authority_status, b1_0.created_at, b1_0.deleted_at, b1_0.description, b1_0.like_count, b1_0.modified_at, b1_0.name, b1_0.status, s1_0.cp_address, s1_0.cp_code, s1_0.cp_email, s1_0.cp_name, s1_0.cp_password, s1_0.created_at, s1_0.deleted_at, s1_0.ep_name, s1_0.modified_at, s1_0.net_proceeds, s1_0.phone_number, s1_0.role, s1_0.status from seller s1_0 left join brand b1_0 on s1_0.seller_id=b1_0.seller_id where s1_0.seller_id=? Hibernate: select sc1_0.sub_category_id, sc1_0.created_at, sc1_0.deleted_at, sc1_0.main_category_id, mc1_0.main_category_id, mc1_0.created_at, mc1_0.deleted_at, mc1_0.modified_at, mc1_0.name, mc1_0.status, sc1_0.modified_at, sc1_0.name, sc1_0.status from sub_category sc1_0 join main_category mc1_0 on mc1_0.main_category_id=sc1_0.main_category_id where sc1_0.sub_category_id=?팬츠였다.총 4*3 = 12번의 추가 select가 되지않고 2번씩만 조회가 되는 것인가?였다.2. 원인
Brand,Seller등의 연관 엔티티가 두 번씩만 조회되고 있다. 이는 N+1 문제의 전형적인 패턴은 아니지만, JPA의 1차 캐시 덕분에 나머지의 10번 이상의 추가 쿼리가 없어진 것으로 판단했다.팬츠에 관한 검색 결과는 다음과 같다.위 두 사진을 종합해서 보면
brand_id(Brand FK) - seller_id(Seller FK) - subcategory_id(SubCategory FK)조합이 product_id = 3, 11에 대해서는1-1-3/ product_id = 4, 12에 대해서는2-2-4구성이 반복됨을 알 수 있다.이렇게 Product 엔티티들이 서로 동일한 Brand, Seller, SubCategory 엔티티를 공유하고 있기 때문에 JPA 1차 캐시에서 재사용된 것이라 볼 수 있다. 즉, 동일한 Brand, Seller가 여러 Product에 연결된 경우 처음에만 쿼리가 발생하고 그 이후에는 캐시된 값을 사용한 것이라 볼 수 있는 것이다.
Hibernate는 처음에만 쿼리를 실행하고, 캐시된 결과를 사용해 추가 쿼리를 방지한다.
3. 해결
FetchType.LAZY적용~toOne으로 기재된JoinColumn들에 대해서 지연로딩을 적용시켰다.~toMany가 붙은 것에 대해서는 Lazy Loading이 Default로 적용되고,~toOne이 붙은 것에 대해서는 Eager Loading이 Default로 적용된다.4. TODO
상품 이름만을 참조하게 해뒀음참고