[JPA] 카테시안 곱 발견 후 해결 과정 (1)

2025. 10. 28. 17:51·Development/DB

최근 프로젝트를 진행하다가 오류가 발생하여 찾아보니 카테시안 곱 때문에 생긴 에러였다.

카테시안 곱에 대해서 알아보고, 오류를 어떻게 해결했는지 작성해보려 한다.

 

 카테시안 곱이란?

 

카테시안 곱이란 쉽게 말해 두 개의 테이블의 모든 행이 서로 짝지어지는 것을 말한다. JPA에서는 데이터베이스 쿼리 사용 시 테이블간의 조인이 잘못되어 발생하며, 예상보다 훨씬 많은 수의 결과 레코드를 생성한다.

 

 

문제 파악

 

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: 
[com.example.adhd_backend.entity.PlanItem.planItemContents, com.example.adhd_backend.entity.
Plan.planItems]

 

프로젝트를 하며 마주한 문제가 이거였다.. MultipleBagFetchException, 카테시안 곱 문제가 발생할 수 있다고 판단되어 Hibernate 가 막는 예외이다.

 

@OneToMany로 매핑된 컬럼들을 join fetch로 동시에 가져오려 해서 발생하였다.

 

@EntityGraph(attributePaths = {"planItems", "planItems.planItemContents"})
@Query("SELECT p FROM Plan p WHERE p.id = :id")
Optional<Plan> findByIdWithItems(@Param("id") Long id);

 

repository에 이런 메서드가 있었다. 이 메서드가 의도하는 바는 Plan을 조회할 때 planItems도 같이 fetch join 하고, planItems 안에 있는 planItemContents도 같이 한 번에 join fetch 하는 것이다.

 

SELECT p.*, pi.*, pic.*
FROM plan p
LEFT JOIN plan_item pi ON p.id = pi.plan_id
LEFT JOIN plan_item_content pic ON pi.id = pic.plan_item_id
WHERE p.id = ?

 

내부적으로는 이런 형식의 sql 쿼리 문이 생성된다.

 

이게 왜 MultipleBagFetchException을 발생시킬까?

plan, plan_item, plan_item_content가 각각 1:N 관계라면, 결과적으로 모든 경우의 수를 생각하기 때문에 N:M 형태의 카테시안 곱이 형성된다. 따라서 Hibernate는 이걸 감지하고 MultipleBagFetchException로 막아버리는 것이다.

 

카테시안 곱이 발생하는 또 다른 이유에서는 다중 조인 시 별칭 혼동, 동적 쿼리에서 조건 문자열이 빠지는 경우 등이 있다.

 

해결 과정  Link -> Set

 

 

우선 첫번째 대안으로 중복을 사전에 걸러서 내보내주는 Set을 사용하였다.

 

Set 은 중복을 허용하는 List와는 달리 같은 Entity가 두 번 들어올 경우 하나로 합쳐진다. 따라서 Collection에는 중복이 남지 않는다. 

 

@OneToMany(mappedBy = "plan", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy("date ASC")
@Builder.Default
private Set<PlanItem> planItems = new LinkedHashSet<>();

 

하지만 이 마저도 모든 문제를 해결하진 못한다..

 

equals() 또는 hashcode()로 객체를 비교하기 때문에 카테시안 곱으로 인한 중복 결과를 어느정도 막을 수 있는 것은 사실이지만, Set은 순서를 보장하지 않는다. (LinkedHashSet으로 순서를 보장할 순 있지만 인덱스 접근이 어려움)

 

즉, 일부 중복 제거는 가능하나, 쿼리 자체가 여전히 Join으로 여러 행을 만들어내기 때문에 카테시안 곱의 부하가 남을 수 있다. (근본적으로 카테시안 곱을 해결하는 방안이 아님!)

 

다음 코드 리팩토링 시에는 Distinct + DTO Projection 조합으로 해결해보겠다.

 

 

 

감사합니다 ( ゚д゚)つ Bye

 

'Development > DB' 카테고리의 다른 글

[MongoDB] Mongoose Transaction 사용하기  (0) 2025.05.16
[Express+MongoDB] 검색 기능 구현 (MongoDB Atlas Search Index)  (0) 2025.04.08
'Development/DB' 카테고리의 다른 글
  • [MongoDB] Mongoose Transaction 사용하기
  • [Express+MongoDB] 검색 기능 구현 (MongoDB Atlas Search Index)
knhye
knhye
  • 전체
    오늘
    어제
  • knhye
    3n1hye_
    knhye
  • 링크

    • GitHub
    • 분류 전체보기 (61)
      • Development (28)
        • Back-end (21)
        • DB (3)
        • CS (4)
      • Algorithm (6)
      • DevOps (10)
        • git (1)
        • Cloud Platform (5)
        • CICD (1)
        • Cloud Native (2)
      • Internet (2)
      • 매일메일 (6)
      • 회고 (5)
        • Capstone (2)
        • Hackathon (1)
        • 2025 (2)
      • 자격증 (1)
      • 블로그 리딩 (3)
  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
knhye
[JPA] 카테시안 곱 발견 후 해결 과정 (1)
상단으로

티스토리툴바