Back-End/JPA

JPA와 QueryDSL: 복잡한 쿼리 효율적으로 작성하기

봉의일상 2025. 3. 9. 22:17

1. QueryDSL이란?

QueryDSL은 타입 안전한 SQL 및 JPQL을 생성할 수 있도록 도와주는 프레임워크입니다. JPQL보다 가독성이 뛰어나고, 컴파일 타임에 오류를 잡을 수 있어 동적 쿼리를 작성할 때 매우 유용합니다.


2. QueryDSL 설정 및 기본 사용법

(1) QueryDSL 설정 (Gradle 기준)

dependencies {
    implementation 'com.querydsl:querydsl-jpa:5.0.0'
    annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jpa'
    annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
}

QueryDSL을 사용하면 Q타입 클래스를 자동 생성해야 하므로, IDE에서 빌드 후 생성된 Q타입 클래스를 확인해야 합니다.


(2) QueryDSL 기본 사용법

1) Q클래스 생성

import com.querydsl.core.types.dsl.EntityPathBase;

QMember member = QMember.member;

2) 단순 조회 쿼리 작성

JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
Member result = queryFactory
    .selectFrom(member)
    .where(member.username.eq("testUser"))
    .fetchOne();

3) 여러 조건을 사용하는 WHERE 절

List<Member> results = queryFactory
    .selectFrom(member)
    .where(
        member.age.gt(18),
        member.status.eq(Status.ACTIVE)
    )
    .fetch();

QueryDSL의 where() 메서드는 여러 조건을 가변 인자로 받을 수 있어 코드가 깔끔해집니다.


3. QueryDSL의 동적 쿼리 작성법

(1) BooleanBuilder 활용

BooleanBuilder를 사용하면 조건을 동적으로 추가할 수 있습니다.

BooleanBuilder builder = new BooleanBuilder();
if (username != null) {
    builder.and(member.username.eq(username));
}
if (age != null) {
    builder.and(member.age.gt(age));
}

List<Member> results = queryFactory
    .selectFrom(member)
    .where(builder)
    .fetch();

(2) Where절 메서드 추출

코드를 더 깔끔하게 만들기 위해 where 절을 별도의 메서드로 추출할 수도 있습니다.

private BooleanExpression usernameEq(String username) {
    return username != null ? member.username.eq(username) : null;
}

private BooleanExpression ageGt(Integer age) {
    return age != null ? member.age.gt(age) : null;
}

List<Member> results = queryFactory
    .selectFrom(member)
    .where(usernameEq(username), ageGt(age))
    .fetch();

이렇게 하면 가독성이 높아지고, 재사용성이 증가합니다.


4. QueryDSL을 활용한 복잡한 쿼리 작성

(1) 조인 (Join) 활용

List<Member> results = queryFactory
    .selectFrom(member)
    .join(member.team, QTeam.team)
    .where(QTeam.team.name.eq("TeamA"))
    .fetch();

(2) 서브쿼리 사용

QMember subMember = new QMember("subMember");
List<Member> results = queryFactory
    .selectFrom(member)
    .where(member.age.eq(
        JPAExpressions
            .select(subMember.age.max())
            .from(subMember)
    ))
    .fetch();

QueryDSL을 활용하면 JPQL에서는 어렵던 서브쿼리도 직관적으로 작성할 수 있습니다.


5. QueryDSL을 이용한 INSERT, UPDATE, DELETE

(1) QueryDSL을 이용한 INSERT

QueryDSL 자체로 INSERT 쿼리를 직접 지원하지 않음. JPA에서는 persist()를 사용하여 INSERT를 수행해야 함.

Member member = new Member();
member.setUsername("newUser");
member.setAge(25);
entityManager.persist(member);

(2) QueryDSL을 이용한 UPDATE

QueryDSL의 update()를 활용하여 벌크 업데이트 수행 가능.

long updatedRows = queryFactory
    .update(member)
    .set(member.age, member.age.add(1))
    .where(member.status.eq(Status.ACTIVE))
    .execute();

주의: 벌크 연산 후 entityManager.clear();로 영속성 컨텍스트 초기화 필요.

(3) QueryDSL을 이용한 DELETE

QueryDSL의 delete()를 활용하여 특정 조건의 데이터를 삭제 가능.

long deletedRows = queryFactory
    .delete(member)
    .where(member.age.lt(18))
    .execute();

주의: DELETE도 벌크 연산이므로 entityManager.flush();entityManager.clear(); 필요.

  QueryDSL 지원여부 방법
INSERT ❌ (지원 안 함) entityManager.persist() 사용
UPDATE ✅ (지원) update().set().where().execute()
DELETE ✅ (지원) delete().where().execute()

6. QueryDSL 사용 시 장점과 주의점

(1) QueryDSL의 장점

타입 안전성: 런타임이 아닌 컴파일 타임에 오류를 잡을 수 있음
가독성: JPQL보다 직관적인 문법
동적 쿼리 최적화: BooleanBuilder, Where절 분리 등을 활용하여 유연한 쿼리 작성 가능
복잡한 SQL 처리 가능: 서브쿼리, GroupBy, Join 등 다양한 SQL 기능 지원

(2) QueryDSL 사용 시 주의점

빌드 시 Q타입 클래스 생성 필요: Q클래스가 자동 생성되지 않으면 빌드를 수동 실행해야 함


7. 결론

QueryDSL은 타입 안전한 동적 쿼리 작성을 지원하며, 복잡한 SQL을 쉽게 작성할 수 있는 강력한 도구입니다.

특히 JPQL보다 가독성이 좋고 유지보수성이 뛰어나기 때문에, Spring Boot + JPA 환경에서는 매우 편리하다.