둘셋 개발!

[API개발과 성능최적화-3] 컬렉션 조회 최적화 본문

JPA

[API개발과 성능최적화-3] 컬렉션 조회 최적화

23 2021. 11. 25. 11:46

컬렉션인 일대다 관계를 조회하고, 최적화 하는 방법이다.

일대다 관계이기 때문에 데이터가 배로 커지기 때문에 문제가 발생하고, 또한 중복도 발생한다


1. 엔티티 조회방식

 

 SQL에 distinct를 추가해서 같은 엔티티가 조회되면, 애플리케이션에서 중복을 걸러준다.

distinct를 추가한 예시

하지만 이에 치명적인 단점은 페이징이 불가능하다는 것이다.

애플리케이션에서는 Order의 id가 같으면 중복을 제거 해주지만, 데이터베이스에서는 한 줄에 있는 모든 데이터가 같아야 중복이 되기 때문에 데이터가 배가 되는 문제는 피할 수가 없다.

따라서 원하는대로 페이징을 할 수 없다.

 


해결방법은 hibernate.default_batch_fetch_size, @BathSize를 적용하는 것이다.

이 옵션을 사용하면 컬렉션이나, 프록시 객체를 한꺼번에 설정한 size만큼 IN쿼리로 조회한다.

 

결론적으로 ToOne관계는 페치조인을 해도 row수가 증가하지 않고 쿼리를 수를 줄일 수 있기 때문에 페치조인으로 최적화를 하고 컬렉션들은 hibernate.default_batch_fetch_size, @BathSize을 사용하면 된다. 만약 컬렉션아 페이징을 필요하지 않으면 페치조인을 하면 된다!!

 

 


2. DTO를 직접 조회

Dto를 직접 조회한다는 것은 다음과 같다.

컬렉션을 제외한 나어지를 DTO로 직접조회

이렇게 findOrders()를 실행하게 되면 return되는 것은 List형태인 OrderQueryDto가 받아진다.

이는 컬렉션을 제외하고 받아진 것(ToOne관계)이고 컬렉션은 별도로 처리한다.

컬렉션을 조회하는 함수를 만들어서 em.createQuery에서 where문에 id가 같은 컬렉션을 조회하고, 

처음에 컬렉션을 제외해서 받아온 결과에서 루프를 돌면서 컬렉션을 추가해주면 된다.

 

하지만 컬렉션을 조회하는 과정에서 1+n문제가 발생한다

 


해결방법은 컬렉션을 in쿼리를 사용해서 한번에 받아들이고, id값을 key로 하는 Map을 만드는 것이다.

Map을 만들 때에는 stream()으로 돌리고, groupingBy를 사용하는 것이다.

 

map을 만드는 과정

 

그리고 ToOne으로 받는 리스트를 루프를 돌리면서 컬렉션을 추가한다

 


마지막 방법이 한가지 더 있는데 데이터를 플랫하게 받아들이는 것인데 가장 큰 장점은 쿼리가 한번이지만 데이터가 중복되기 때문에 성능이 안좋아질 수도 있고 페이징이 불가능하다

 

 



마무리!!

김영한 강사님이 추천하시는 권장순서는 다음과 같다

1. 엔티티 조회 방식으로 우선접근

2. 엔티티 조회 방식으로 해결이 안되면 DTO 조회 방식 사용

3. DTO조회 방식으로 해결이 안되면 NativeSQL이나 스프링 jdbcTemplate

 

엔티티 조회방식으로 우선 접근하는 이유는

엔티티 조회방식은 코드를 거의 수정하지 않고, 옵션만 약간 변경해서 다양한 성능 최적화를 시도할 수 있지만 

Dto직접 조회방식은 성능 최적화를 하거나 방식을 변경할 때 많은 코드를 변경해야 하기 때문이다.

 

 

(참고 : 인프런 김영한 강사님 - 실전! 스프링 부트와 jpa활용2)