반응형
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개만 제대로 쓰면 실무에서 대부분 해결된다.
반응형
'백엔드 개발' 카테고리의 다른 글
| Spring Boot DTO record 사용법 | 언제 써야 하고 왜 쓰는지 정리 (0) | 2026.04.23 |
|---|---|
| 서킷 브레이커(Circuit Breaker) 완벽 정리 + Spring Boot 적용 (Resilience4j) (2) | 2026.04.15 |
| Spring Boot Security 4.x 인증/인가 구현 (Resource · Menu · Action 기반 권한 설계) (0) | 2026.04.13 |
| JPA MyBatis 집계 한계와 Spring Batch로 해결하는 대용량 집계 처리 (0) | 2026.04.09 |
| Spring Snowflake ID 생성기 실무 적용기 (Auto Increment 한계와 해결 방법) (0) | 2026.04.08 |