본문 바로가기
백엔드 개발

Spring Boot Querydsl 설정부터 실무 사용까지 (동적쿼리, 페이징 예제)

by collenkim 2026. 4. 14.
반응형

Spring Boot에서 JPA를 사용하다 보면 금방 한계가 온다.
특히 검색 조건이 늘어나고, 필터가 동적으로 바뀌는 순간부터 JPQL은 관리가 어려워진다.

이 문제를 해결하는 대표적인 도구가 Querydsl이다.


1. Querydsl을 왜 사용하는가

JPA + JPQL만 사용할 때 대표적인 문제:

  • 문자열 기반 쿼리 → 오타가 컴파일에서 잡히지 않음
  • 조건이 많아질수록 가독성 급격히 하락
  • 동적 쿼리 작성이 복잡함
  • 유지보수 비용 증가

예시:

@Query("select c from Customer c where (:name is null or c.name = :name)")

 

조건이 늘어나면 사실상 관리 불가능한 수준이 된다.

 

 


2. Querydsl은 언제 필요한가

다음 상황이면 거의 무조건 쓴다:

  • 검색 조건이 동적으로 바뀌는 API
  • 관리자 페이지 필터 검색
  • 페이징 + 정렬 필수
  • JOIN이 많은 조회 로직

즉, 조회 로직이 복잡해지는 순간부터 필요하다.


3. Querydsl이 좋은 이유

핵심만 보면 이거다:

  • 타입 안전성 (컴파일 시점 체크)
  • 동적 쿼리 작성이 자연스러움
  • 코드 기반 → 유지보수 쉬움
  • IDE 자동완성 지원

4. Gradle 설정 (Spring Boot 4.x)

Boot 4는 기본적으로 Jakarta 기반이다.
Querydsl도 jakarta 버전으로 맞춰야 한다.

dependencies {
    implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'

    annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
    annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
}

5. Q-class 생성 설정 (핵심)

def querydslDir = "$buildDir/generated/querydsl"

sourceSets {
    main {
        java {
            srcDirs += querydslDir
        }
    }
}

tasks.withType(JavaCompile).configureEach {
    options.generatedSourceOutputDirectory = file(querydslDir)
}

6. IntelliJ 설정

  • Settings → Compiler → Annotation Processors
  • ✔ Enable annotation processing

7. 엔티티 작성

@Entity
public class Customer {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private String type;
}

 

빌드 후 생성

QCustomer customer = QCustomer.customer;

8. Querydsl 기본 사용

@RequiredArgsConstructor
@Repository
public class CustomerQueryRepository {

    private final JPAQueryFactory queryFactory;

    public List<Customer> findAll() {

        QCustomer customer = QCustomer.customer;

        return queryFactory
                .selectFrom(customer)
                .fetch();
    }
}

9. 실무 핵심 – 동적 쿼리

public List<Customer> search(String name, String type) {

    QCustomer customer = QCustomer.customer;

    return queryFactory
            .selectFrom(customer)
            .where(
                nameEq(name),
                typeEq(type)
            )
            .fetch();
}

private BooleanExpression nameEq(String name) {
    return name == null ? null : QCustomer.customer.name.eq(name);
}

private BooleanExpression typeEq(String type) {
    return type == null ? null : QCustomer.customer.type.eq(type);
}

 

포인트:

  • null 조건 자동 무시
  • 조건 추가가 쉬움

10. 페이징 처리

public List<Customer> findPage(int offset, int limit) {

    QCustomer customer = QCustomer.customer;

    return queryFactory
            .selectFrom(customer)
            .orderBy(customer.id.desc())
            .offset(offset)
            .limit(limit)
            .fetch();
}

11. Count 쿼리 분리 (중요)

public long count() {

    QCustomer customer = QCustomer.customer;

    return queryFactory
            .select(customer.count())
            .from(customer)
            .fetchOne();
}

12. 실무에서 많이 하는 구조

  • 조회 쿼리 (content)
  • 카운트 쿼리 (count)

둘을 나눠야 성능이 안정적이다.


13. 자주 터지는 문제

Q-class 안 생성됨

  • annotation processing 꺼짐
  • gradle build 안 됨

cannot find Q 클래스

  • clean build 필요
  • IDE 캐시 문제

fetch join + paging 문제

  • 컬렉션 fetch join + 페이징 같이 쓰면 데이터 꼬임

14. 정리

Querydsl은 단순히 “편한 쿼리 도구”가 아니다.
복잡한 조회 로직을 관리 가능하게 만드는 도구다.

핵심만 기억하면 된다:

  • Q-class 생성 구조 이해
  • BooleanExpression 동적 조건
  • 페이징 + count 분리

이 3개만 제대로 쓰면 실무에서 대부분 해결된다.

반응형