지연 로딩과 조회 성능 최적화
주문 + 배송정보 + 회원을 조회하는 API를 만들자.
지연 로딩 때문에 발생하는 성능 문제를 단계적으로 해결해보자.
지금부터 하는 설명은 정말 중요하다고 언급
잘 안들으면 인생 허비스
V1 - 엔티티 직접 노출
음... 이렇게 쓰면 엔티티가 외부에 노출되는데, 이때 Member
-> Order
-> Member
-> ... 무한루프를 돌게 된다.
그러므로 엔티티를 직접 노출할 때는 양방향 연관관계가 걸린 곳은 꼭 한곳을 @JsonIgnore
처리해야 한다.
하지만, 그냥 DTO
를 쓰는 것이 훨씬 좋다.
@JsonIgnore
를 사용해도, LAZY
설정을 했다면, 해당 객체를 프록시로 생성하기 때문에 이것과 관련된 바이트예외가 던져진다. 이를 해결하기 위해
강의에서 Hibernate5Modle
을 사용해서 더 수월하게 보내는 방법을 설명했지만, 그냥 DTO
로 따로 보내는것이 훨씬 유지보수에 좋아 보인다.
V2 - 엔티티 DTO 변환
이 부분의 문제는
반복문을 돌면서
Order
하나 찾는 쿼리를 동작하면서
order.getMember().getName();
order.getDelivery().getAddress();
이 부분을 돌때마다 쿼리를 돌려야한다.
즉, 하나의 상품을 돌때 쿼리를 1 + 2번 날려야한다.
두개의 상품은 쿼리를 1 + 4번 날려야한다.
10개면 1 + 20번 날려야한다.
이게 N + 1 문제이다.
그러므로 성능에 문제가 발생할 수 있다는 것.
V3 - 페치 조인 최적화
이렇게 할 경우, 쿼리를 처음 한 번만 실행하게 된다.
V2
2022-03-17 09:18:13.346 DEBUG 72007 --- [nio-8080-exec-5] org.hibernate.SQL :
select
order0_.order_id as order_id1_6_,
order0_.delivery_id as delivery4_6_,
order0_.member_id as member_i5_6_,
order0_.order_date as order_da2_6_,
order0_.status as status3_6_
from
orders order0_
inner join
member member1_
on order0_.member_id=member1_.member_id limit ?
Hibernate:
select
order0_.order_id as order_id1_6_,
order0_.delivery_id as delivery4_6_,
order0_.member_id as member_i5_6_,
order0_.order_date as order_da2_6_,
order0_.status as status3_6_
from
orders order0_
inner join
member member1_
on order0_.member_id=member1_.member_id limit ?
2022-03-17 09:18:13.396 DEBUG 72007 --- [nio-8080-exec-5] org.hibernate.SQL :
select
member0_.member_id as member_i1_4_0_,
member0_.city as city2_4_0_,
member0_.street as street3_4_0_,
member0_.zipcode as zipcode4_4_0_,
member0_.name as name5_4_0_
from
member member0_
where
member0_.member_id=?
Hibernate:
select
member0_.member_id as member_i1_4_0_,
member0_.city as city2_4_0_,
member0_.street as street3_4_0_,
member0_.zipcode as zipcode4_4_0_,
member0_.name as name5_4_0_
from
member member0_
where
member0_.member_id=?
2022-03-17 09:18:13.403 DEBUG 72007 --- [nio-8080-exec-5] org.hibernate.SQL :
select
delivery0_.delivery_id as delivery1_2_0_,
delivery0_.city as city2_2_0_,
delivery0_.street as street3_2_0_,
delivery0_.zipcode as zipcode4_2_0_,
delivery0_.status as status5_2_0_
from
delivery delivery0_
where
delivery0_.delivery_id=?
Hibernate:
select
delivery0_.delivery_id as delivery1_2_0_,
delivery0_.city as city2_2_0_,
delivery0_.street as street3_2_0_,
delivery0_.zipcode as zipcode4_2_0_,
delivery0_.status as status5_2_0_
from
delivery delivery0_
where
delivery0_.delivery_id=?
2022-03-17 09:18:13.408 DEBUG 72007 --- [nio-8080-exec-5] org.hibernate.SQL :
select
member0_.member_id as member_i1_4_0_,
member0_.city as city2_4_0_,
member0_.street as street3_4_0_,
member0_.zipcode as zipcode4_4_0_,
member0_.name as name5_4_0_
from
member member0_
where
member0_.member_id=?
Hibernate:
select
member0_.member_id as member_i1_4_0_,
member0_.city as city2_4_0_,
member0_.street as street3_4_0_,
member0_.zipcode as zipcode4_4_0_,
member0_.name as name5_4_0_
from
member member0_
where
member0_.member_id=?
2022-03-17 09:18:13.414 DEBUG 72007 --- [nio-8080-exec-5] org.hibernate.SQL :
select
delivery0_.delivery_id as delivery1_2_0_,
delivery0_.city as city2_2_0_,
delivery0_.street as street3_2_0_,
delivery0_.zipcode as zipcode4_2_0_,
delivery0_.status as status5_2_0_
from
delivery delivery0_
where
delivery0_.delivery_id=?
Hibernate:
select
delivery0_.delivery_id as delivery1_2_0_,
delivery0_.city as city2_2_0_,
delivery0_.street as street3_2_0_,
delivery0_.zipcode as zipcode4_2_0_,
delivery0_.status as status5_2_0_
from
delivery delivery0_
where
delivery0_.delivery_id=?
V3
2022-03-17 09:19:05.854 DEBUG 72007 --- [nio-8080-exec-6] org.hibernate.SQL :
select
order0_.order_id as order_id1_6_0_,
member1_.member_id as member_i1_4_1_,
delivery2_.delivery_id as delivery1_2_2_,
order0_.delivery_id as delivery4_6_0_,
order0_.member_id as member_i5_6_0_,
order0_.order_date as order_da2_6_0_,
order0_.status as status3_6_0_,
member1_.city as city2_4_1_,
member1_.street as street3_4_1_,
member1_.zipcode as zipcode4_4_1_,
member1_.name as name5_4_1_,
delivery2_.city as city2_2_2_,
delivery2_.street as street3_2_2_,
delivery2_.zipcode as zipcode4_2_2_,
delivery2_.status as status5_2_2_
from
orders order0_
inner join
member member1_
on order0_.member_id=member1_.member_id
inner join
delivery delivery2_
on order0_.delivery_id=delivery2_.delivery_id
Hibernate:
select
order0_.order_id as order_id1_6_0_,
member1_.member_id as member_i1_4_1_,
delivery2_.delivery_id as delivery1_2_2_,
order0_.delivery_id as delivery4_6_0_,
order0_.member_id as member_i5_6_0_,
order0_.order_date as order_da2_6_0_,
order0_.status as status3_6_0_,
member1_.city as city2_4_1_,
member1_.street as street3_4_1_,
member1_.zipcode as zipcode4_4_1_,
member1_.name as name5_4_1_,
delivery2_.city as city2_2_2_,
delivery2_.street as street3_2_2_,
delivery2_.zipcode as zipcode4_2_2_,
delivery2_.status as status5_2_2_
from
orders order0_
inner join
member member1_
on order0_.member_id=member1_.member_id
inner join
delivery delivery2_
on order0_.delivery_id=delivery2_.delivery_id
한 번에 전부 조회하여 넣어주기 때문에 쿼리를 한 번만 사용하는 것이다.
그로 인해 성능도 굉장히 빨라지게 된다.
하지만, 모든 데이터를 찍어야하는 단점이 존재한다.
V4 - JPA에서 DTO 바로 조회
2022-03-17 09:38:13.036 DEBUG 73510 --- [nio-8080-exec-5] org.hibernate.SQL :
select
order0_.order_id as col_0_0_,
member1_.name as col_1_0_,
order0_.order_date as col_2_0_,
order0_.status as col_3_0_,
delivery2_.city as col_4_0_,
delivery2_.street as col_4_1_,
delivery2_.zipcode as col_4_2_
from
orders order0_
inner join
member member1_
on order0_.member_id=member1_.member_id
inner join
delivery delivery2_
on order0_.delivery_id=delivery2_.delivery_id
Hibernate:
select
order0_.order_id as col_0_0_,
member1_.name as col_1_0_,
order0_.order_date as col_2_0_,
order0_.status as col_3_0_,
delivery2_.city as col_4_0_,
delivery2_.street as col_4_1_,
delivery2_.zipcode as col_4_2_
from
orders order0_
inner join
member member1_
on order0_.member_id=member1_.member_id
inner join
delivery delivery2_
on order0_.delivery_id=delivery2_.delivery_id
딱 필요한 데이터만 정확히 쿼리로 가져와서 Dto
에 넣어주는 방식이다.
더 최적화되었지만, V3
이 좋은지 V4
가 좋은지는 가리기가 애매하다.
V3
의 경우에는 fetch join
로 인해 결과를 만들었고, 이 데이터들은 재사용성이 가능하다. 단점은 한번에 많은 데이터를 가져온다는 점이다.
V4
의 경우에는 딱 원하는 데이터만 가져오기 때문에 성능성에서는 좋다는 장점이 있다. 하지만, 이 Dto
전용이므로, 재사용을 할 수 없고, 코드도 지져분하다는 단점을 가지고 있다.
또, V3
의 경우에는 Order
엔티티를 가져오기 때문에 계층이 유지되지만, V4
에서 리포지토리에 다른 Dto
를 사용한다는 것 자체가 논리적으로 계층이 깨진다는 것을 볼 수 있다.
강사의 개인적인 의견으로는 V3
와 V4
는 사실상 성능차이가 크지 않기 때문에 V3
를 선호하는 것 같다. 요즘에는 네트워크도 좋다는 점과, select
가 많이 들어간다고 해서 성능이 많이 저하되지는 않고, 그 밑의 from
이나 다른 명령어들이 성능을 먹는 것이라고 말씀하심.
만약 V4
를 사용하고 싶다면,
해당 메서드를 OrderSimpleQueryRepository
를 통해 리포지토리를 하나 만드는 것이 좋다고 하심.
권장 순서
1. 우선 엔티티를 DTO로 변환하는 방법을 선택한다.
2. 필요하면 패치 조인(V3
)으로 성능을 최적화 한다. -> 대부분의 성능 이슈 해결
3. 그래도 안되면 DTO로 직접 조회하는 방법을 사용.(V4
)
4. 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용.
Author And Source
이 문제에 관하여(지연 로딩과 조회 성능 최적화), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@seungju0000/지연-로딩과-조회-성능-최적화저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)