QueryDSL 사용 시 Cartesian Product 주의

이규정·2025년 3월 13일

1. Cartesian Product 정의


두 개 이상의 집합에서 각 집합의 모든 원소를 서로 조합해서 가능한 모든 조합을 만들어내는 연산. 두 테이블 간 JOIN 조건이 불충분하거나 누락될 경우 불필요한 데이터가 기하급수적으로 증가할 수 있음. (join 조건을 1=1 식으로 지정하면, 데이터 N개의 테이블과 데이터 M개의 테이블을 join 할 경우 N x M 개의 데이터 호출)

2. QueryDSL의 동작 방식


QueryDSL은 Java에서 타입 세이프한 쿼리를 작성할 수 있도록 지원하는 프레임워크. 코드 수준에서 SQL을 추상화하여 작성할 수 있음.
이때, QueryDSL은 테이블을 join 할때 엔티티 간의 연관관계를 기반으로 join 하도록 설계되어 있음.

예시 SQL:

SELECT t.*
FROM todo t
LEFT JOIN manager m ON t.id = m.todo_id
LEFT JOIN user u ON m.user_id = u.id
WHERE t.title LIKE '%title%' AND u.nickname LIKE '%manager%';

위 SQL을 QueryDSL로 표현하면:

List<Todo> todos = jpaQueryFactory
    .selectFrom(todo)
    .leftJoin(todo.managers, manager)
    .leftJoin(manager.user, user)
    .where(todo.title.contains("title"), user.nickname.contains("manager"))
    .fetch();

3. QueryDSL 사용 시 Cartesian Product 발생 주의


일대다 관계를 여러 번 JOIN할 때 중복 데이터가 생성되는 Cartesian product가 발생할 가능성이 큼. - [프로젝트] 카테시안 곱 문제의 발생과 해결

QueryDSL을 사용할 때 아래 사항을 기억할 것

  • JOIN 시 엔티티 간의 관계를 명확히 설정할 것
  • 예를 들어, .leftJoin(todo.managers.id, manager.id)와 같이 id로 직접 조인하면 오히려 동작이 불안정하므로 피할 것. (엔티티 기준으로 JOIN해야 함)
  • 일대다 JOIN을 여러 번 해야 하는 경우, 쿼리를 분리하거나 DISTINCT 및 countDistinct() 활용할 것

실제 예시로, countDistinct를 활용하는 법

Long total = jpaQueryFactory
    .select(todo.countDistinct())
    .from(todo)
    .leftJoin(todo.managers, manager)
    .leftJoin(manager.user, user)
    .where(condition)
    .fetchOne();
profile
반갑습니다. 백엔드 개발자가 되기 위해 노력중입니다.

0개의 댓글