JAVA/Spring Boot

Join을 Map으로 최적화

주코식딩 2023. 2. 23. 16:31

기존 이름은 'Join시 n+1 문제 Map으로 해결' 이었으나 생각해보니 n+1 문제가 아니라 그냥 최적화 문제이므로 수정했다.


회사에서 사용중인 DB는 외래키가 존재하지 않고 양이 워낙 많아(테이블만 280개..) JPA를 활용한 batch_fetch_size의 기능을 쓰기 어렵다. 

따라서 이를 자료구조로 해결하고자 방향을 잡고 생각해보니 Map을 활용하면 꽤나 효율적으로 작업을 할 수 있을 것 같았다.

 

 

부모 spot 테이블과 자식 spotImg, spotTheme 테이블을 예로 들어 설명해보겠다.

 

일단 spot에서 필요한 데이터만 가지고 온다. (자세한 쿼리는 보안상 생략)

List<FirstSpotJoinDto> spotListByCondition = spotRepository.getSpotListByCondition(spotId, seId);

 

자식 데이터를 WHERE IN 절을 통해 가지고 온다. (batch_fetch_size의 동작 방식에서 아이디어를 얻었다.)

WHERE IN 절을 사용하기 위해 spotListByCondition 에서 spotId만 추출해 List로 만든다.

List<Integer> spotIdList = spotListByCondition.stream().map(FirstSpotJoinDto::getSpotId).toList();

List<SpotImg> spotImgList = spotImgRepository.findBySpotIdInAndState(spotIdList, "Y");
List<SpotThemeRel> spotThemeList = spotThemeRelRepository.findBySpotIdInAndStateAndSubState(spotIdList, "Y", "Y");

 

이 데이터들을 spotId를 키로 갖는 MultiValueMap에 등록한다.

MultiValueMap을 쓰는 이유는 단순하게 이들이 1:N 관계이기 때문이다. (key : 1 , value : N)

시간복잡도 O(N)으로 문제를 해결할 수 있게 된다.

join시 발생하는 중복되는 부모의 데이터도 받을 필요가 없다.

MultiValueMap<Integer, List<String>> imgMap = new LinkedMultiValueMap<>();
for (SpotImg spotImg : spotImgList) {
    imgMap.add(spotImg.getSpotId(), Arrays.asList(spotImg.getUrl(), String.valueOf(spotImg.getOnum())));
}
MultiValueMap<Integer, Integer> themeMap = new LinkedMultiValueMap<>();
for (SpotThemeRel theme : spotThemeList) {
    themeMap.add(theme.getSpotId(), theme.getThemeId());
}

 

이제 처음에 가져왔던 spotListByCondition 을 루프로 돌면서 비지니스 로직을 처리하면 된다.

자식의 데이터가 필요하다면 그저 Map에 spotId를 넣어서 O(1)의 시간복잡도로 데이터를 얻을 수 있다.