실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화 - 섹션4. API 개발 고급 - 컬렉션 조회 최적화
CS/김영한 스프링 강의

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화 - 섹션4. API 개발 고급 - 컬렉션 조회 최적화

이제 일대다 조회(컬렉션 조회)해서 데이터 막 복제되고 하는곳에 성능 최적화를 해보자.

 

OneToMany를 반환할거다

이렇게 엔티티를 직접 반환했지만 이것도 dto로 바꿀꺼다.

근데 안의 내용들까지 dto로 바꾸는게 쉽게 안된다. 그래서 따로 섹션 뺀거다.

 

일단 겉을 dto로 했을 때

orderItems가 null이 나왔는데 지연 로딩이라 안불러와서 그렇다. 하나하나 불러와주는것도 넣어주고 다시 해보면 잘 나온다.

 

근데 문제는 사실 저것도 엔티티 그대로인 것이다. 즉, 겉은 dto로 바꿨는데 안은 엔티티를 반환하고 있는것.

안에도 dto로 변환해보자.

 

 

 

이제 아까 했던 것처럼 fetch join을 사용해서 쿼리 최적화를 해보자.

 

 

근데 이렇게만 했을 때 일대다일때 여러 번 언급했던 데이터 증식 문제가 나온다.

 

실제로 출력을 보면 증식해서 나온다.

이게 어떻게 된 일인지 db sql 쿼리를 조금 알아보자.

 

join을 했을 때 중간에서 자기 자신을 여러개 가지고 있으므로 자신은 하나인데 최종 결과는 증식한다. 실제 sql 출력된걸 그대로 입력해도 그렇다.

 

심지어 출력된 객체조차 그렇다. hibernate는 이미 이게 중복인지 알고 있지만 db에서 그대로 온 출력을 자기가 마음대로 자를 순 없으므로 그대로 반영하는 것.

 

이를 해결하려면 distinct를 사용하여 중복을 제거한다. 사실 db에서는 완전 중복이 아니면 그대로라 db에서의 갯수는 여전하지만 distinct를 사용함으로써 hibernate에게 중복이 있으면 제거하라는 의도를 주었기 때문에 자기가 제거한다.

 

 

아직 남아있는 문제점이 있다.

하나는 일대다를 사용하면 페이징이 안된다는 것과 두번째는 이렇게 일대다는 하나만 fetch가 가능한 것.

 

 

실제로 페이징을 시도하려고 setFirstResult와 MaxResults를 사용하려고 하면 sql에선 뭐 limit 이런거 없이 그대로 쓰고 메모리에 일단 다 가져와 페이징을 하기 때문에 메모리가 초과될 수도 있다는 경고 문구를 띄운다. 안되는 이유는 distinct를 쓰든 어쨌든 db에서 나온 결과는 그대로이기 때문에 db에서 중복 처리를 할 수 없기 때문이다.

 

그래서 어떻게 이 페이징이 안되는 문제를 해결하는지 본다..

 

 

바로 xToMany가 나오는 fetch 이후는 전부 버리고 그 전의 xToOne fetch들 까지만 가져오는 거다. 그럼 저정도 까지는 한번에 불러오고 그 이후는 where를 통해 일일이 가져올 것이다.

 

하지만 증강 요소를 아얘 차단했으므로 갯수는 일치할 것이고, 페이징 사용이 가능하다.

 

 

일일히 불러오는 거는 감수하고 쓰는거.. 이걸 보완하려고 batch fetch size를 설정한다. 최대 1000까지만 가능한데 보통 100, 300정도를 추천하는 듯.

 

그럼 일일히 where로 불러오지 않고 처음 where로 불러올 때 batch size로 정한 만큼을 미리 불러와 영속성 컨텍스트에 저장한다.

 

 

일대다를 하게되면 문제가 db는 많은쪽으로 데이터를 생성하기 때문에 데이터 증강이 생기는 것. 그래서 XtoOne은 자기가 많기 때문에 문제가 안되지만 XtoMany는 남이 나보다 더 많아서 데이터 증강이 된다는걸 알자.

 

 

직접 dto 조회로 바꾸기

result에서 컬렉션 부분은 일일히 가져오는거에 주목. 여기서 N+1 문제가 생긴다.

앞에서 했던거랑 비슷하다.

 

N+1 최적화 해보자.

where를 통해 몇번씩 불러오는걸 내가 미리 in으로 모두 불러와 영속성 컨텍스트에 저장해놔서 다음에 불러오는 것도 여기서 불러오게끔 하는 것이다. 그래서 쿼리는 2번 나가는 것.

 

페이징을 희생하면 이걸 쿼리 1번으로도 줄일 수 있다.

 

반환하는 dto를 다른것과 통일하고 싶다면 머리를 좀 써야 한다. 앱 내에서 변환해줘야 함.

 

groupby에서 객체끼리 묶을려면 해시코드를 지정해줘야 한다.