카테고리 계층형 쿼리 조회 이슈 해결

2024-01-29
수정

블로그 서비스에서 블로그가 ManyToOne 관계이고, 계층형(계단식) 쿼리 구조로 설계된 카테고리를 객체 형식으로 만들려고 하면서 겪은 이슈 해결 과정이다.

발단

List<BlogCategoryEntity> findAllByParentIsNull();

처음 구현하려고 하였을 때, JPA의 배치사이즈를 설정해 두었으니, 부모가 없는 가장 상위 행들을 불러와서 DTO로 매핑하면, 자식들을 한 번에 불러올 것으로 생각했다.

위기

findAllByParentIsNull:

Hibernate: select b1_0 from blog_category b1_0 where b1_0.parent_id is null
Hibernate: select c1_0 from blog_category c1_0 where c1_0.parent_id in (?)
Hibernate: select c1_0 from blog_category c1_0 where c1_0.parent_id in (?)
Hibernate: select c1_0 from blog_category c1_0 where c1_0.parent_id=?

findAll:

Hibernate: select b1_0 from blog_category b1_0
Hibernate: select c1_0 from blog_category c1_0 where c1_0.parent_id in (?)

생각과 다르게 쿼리가 많이나갔다. n+1 문제가 발생한 것이다. 그래서 일단 다 조회해 오기로 마음먹었다. 근데 잉? 생각해 보니 OneToMany의 상황에 fetch=Lazy로 설정해 두어서 한 번에 불러오지도 못 하고, 매핑하니 모든 객체의 자식이 있는지 확인하려고 이번엔 객체 개수에 비례해서 쿼리를 날리고 있었다. 내 눈에는 이미 모든 객체가 있으니 영속성 컨텍스트가 알아서 매핑해주려나 쉽게 생각했다가 봉변당하고 만 것이다.

해결

나머지는 로직에서

    @Transactional(readOnly = true)
    public List<BlogCategory> getBlogCategoryList() throws Exception {
        List<BlogCategoryEntity> blogCategoryEntities = blogCategoryRepository.findAllByOrderByName();

        List<BlogCategory> blogCategories = new ArrayList<>();
        Map<UUID, BlogCategory> blogCategoryMap = new HashMap<>();

        blogCategoryEntities.forEach(entity -> convertEntityToDto(entity, blogCategoryMap, blogCategories));

        return blogCategories;
    }

    private void convertEntityToDto(BlogCategoryEntity entity, Map<UUID, BlogCategory> blogCategoryMap,
            List<BlogCategory> blogCategories) {
        BlogCategory blogCategory = BlogCategoryMapper.INSTANCE.toBlogCategory(entity);
        blogCategoryMap.put(blogCategory.getId(), blogCategory);
        if (entity.getParent() != null)
            blogCategoryMap.get(entity.getParent().getId()).getChild().add(blogCategory);
        else
            blogCategories.add(blogCategory);
    }

하는 수 없지 다 불러와서 로직으로 처리하기로 했다. HashMap을 옆에 두고 forEach로 한번씩만 순회해서 부모가 있으면 자식을 추가하고, 부모가 없으면 리스트에 추가하도록 구현하였다.