PromleeBlog
sitemap
aboutMe

posting thumbnail
타입 세이프 쿼리, QueryDSL 시작하기
Getting Started with Type-Safe Queries, QueryDSL

📅

🚀

들어가기 전에 🔗

이번 시간에는 JPA를 사용하는 개발자라면 거의 필수적으로 마주하게 되는 강력한 도구, 바로
QueryDSL
에 대해 알아보겠습니다.
JPA는 매우 편리한 기술이지만, 복잡한 쿼리를 작성해야 할 때는 종종 한계에 부딪히곤 합니다.
특히, SQL과 유사한 JPQL을 문자열 형태로 작성하는 방식은 사소한 오타 하나가 런타임 에러로 이어지는 아찔한 상황을 만들곤 합니다.

QueryDSL은 이러한 문제를 해결하기 위해 등장했습니다.
마치 자바 코드를 작성하듯이
타입에 안전하게(Type-Safe)
쿼리를 만들 수 있게 해주어, 컴파일 시점에 오류를 잡고 생산성을 극적으로 높여주는 쿼리 빌더(Query Builder)입니다.
오늘은 QueryDSL이 왜 필요하고, 어떻게 설정하며, 실제로 어떻게 사용하는지 그 모든 것을 알아보겠습니다.

🚀

문자열 기반 쿼리의 한계 (JPQL) 🔗

JPA에서 복잡한 쿼리를 작성할 때, 우리는 보통 @Query 어노테이션 안에 JPQL이라는 쿼리 언어를 문자열로 작성합니다.
@Query("SELECT m FROM Member m WHERE m.username = :username")
Member findMemberByUsername(@Param("username") String username);
이 방식은 몇 가지 명확한 단점을 가집니다.

🚀

QueryDSL, 자바 코드로 쿼리 작성하기 🔗

QueryDSL은 이러한 문자열 기반 쿼리의 모든 문제를 해결합니다.
자바 코드를 통해 쿼리를 생성
하기 때문에, 다음과 같은 강력한 장점들을 얻을 수 있습니다.

QueryDSL 기본 설정과 Q-Type 🔗

QueryDSL을 사용하려면 약간의 초기 설정이 필요합니다.
핵심은
Q-Type
이라는 특별한 클래스를 생성하는 것입니다.
  1. 의존성 추가
    build.gradle 파일에 QueryDSL 관련 라이브러리 의존성을 추가합니다.
    // build.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"
        annotationProcessor "jakarta.annotation:jakarta.annotation-api"
        // ...
    }
  2. Q-Type 생성
    프로젝트를 빌드(컴파일)하면, annotationProcessor(APT)가 우리가 만든 엔티티(@Entity) 클래스들을 분석하여, 쿼리를 위한 메타 모델 클래스인
    Q-Type
    을 자동으로 생성해 줍니다.
    예를 들어, Member라는 엔티티가 있다면, QMember라는 클래스가 generated 폴더 아래에 만들어집니다.
    QMember 클래스는 Member 엔티티의 구조를 그대로 따라가며, 쿼리를 위한 다양한 메서드들을 제공합니다.

🚀

QueryDSL 기본 문법 🔗

먼저, JPAQueryFactory를 Bean으로 등록하여 프로젝트 어디서든 주입받아 사용할 수 있도록 설정해야 합니다.
@Configuration
public class QuerydslConfig {
    @PersistenceContext
    private EntityManager entityManager;
 
    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}
이제 이 JPAQueryFactory를 사용하여 쿼리를 작성해 보겠습니다.
// JPAQueryFactory를 주입받습니다.
private final JPAQueryFactory queryFactory;
 
// QMember를 static import하여 사용하면 편리합니다.
import static com.example.project.entity.QMember.member;
 
public Member findByUsername(String username) {
    return queryFactory
            .selectFrom(member) // QMember 인스턴스를 사용합니다.
            .where(member.username.eq(username)) // 타입에 안전한 조건절
            .fetchOne(); // 결과가 하나일 때 사용
}
위 코드는 마치 자바의 메서드 체이닝처럼 읽히며, 모든 요소가 자바 코드이므로 오타나 문법 오류가 있다면 즉시 컴파일러가 알려줍니다.

동적 쿼리 작성하기 🔗

QueryDSL의 진정한 강력함은 동적 쿼리에서 드러납니다.
BooleanBuilder나 메서드를 활용한 조건 조합으로 매우 깔끔한 코드를 작성할 수 있습니다.
public List<Member> search(String name, Integer age) {
    BooleanBuilder builder = new BooleanBuilder();
 
    if (name != null) {
        builder.and(member.username.contains(name));
    }
    if (age != null) {
        builder.and(member.age.goe(age)); // goe: Greater Or Equal (>=)
    }
 
    return queryFactory
            .selectFrom(member)
            .where(builder)
            .fetch(); // 결과가 여러 개일 때 사용
}

🚀

주요 면접 예상 질문 🔗

QueryDSL은 실무에서 매우 널리 쓰이므로, 그 장점과 원리를 묻는 질문이 자주 나옵니다.

1. JPQL 대신 QueryDSL을 사용하는 가장 큰 이유는 무엇인가요? 🔗

두 방식의 근본적인 차이점을 설명하는 것이 핵심입니다.
가장 큰 이유는
타입 안전성(Type-Safety)을 확보
하기 위함입니다.
JPQL은 문자열 기반이므로 오타나 문법 오류를 컴파일 시점에 잡을 수 없고, 이는 런타임 에러로 이어질 수 있습니다.
반면, QueryDSL은 모든 쿼리를 자바 코드로 작성하기 때문에, 컴파일러가 오류를 즉시 잡아주어 애플리케이션의 안정성을 크게 높일 수 있습니다.
또한, IDE의 자동 완성 및 리팩토링 지원을 받을 수 있어 생산성도 향상됩니다.

2. QueryDSL의 Q-Type은 어떻게 생성되고 어떤 역할을 하나요? 🔗

내부 동작 원리를 이해하고 있는지 확인하는 질문입니다.
Q-Type 클래스는
컴파일 시점에 어노테이션 프로세서(APT)가 @Entity 어노테이션이 붙은 엔티티 클래스를 분석하여 자동으로 생성
하는 쿼리용 메타 모델입니다.
이 Q-Type 클래스는 원본 엔티티의 필드들을 그대로 가지고 있으며, 각 필드에 대해 eq, contains, goe와 같은 다양한 쿼리 메서드를 제공합니다.
개발자는 이 Q-Type을 사용하여, 마치 일반적인 자바 객체를 다루듯이 안전하고 직관적으로 쿼리를 작성할 수 있습니다.

🚀

결론 🔗

오늘은 JPA의 한계를 극복하고, 안전하고 즐겁게 쿼리를 작성할 수 있게 해주는 QueryDSL에 대해 알아보았습니다.

참고 🔗