@Query
어노테이션 안에 JPQL이라는 쿼리 언어를 문자열로 작성합니다.@Query("SELECT m FROM Member m WHERE m.username = :username")
Member findMemberByUsername(@Param("username") String username);
Member
를 Memeber
로 잘못 쓰거나, username
을 userName
으로 잘못 쓰면 어떻게 될까요?
컴파일러는 이것이 단순한 문자열이라고 생각하기 때문에 아무런 오류를 알려주지 않습니다.
오류는 애플리케이션이 실행되고 해당 쿼리가 호출되는 시점에야 비로소 발생합니다.Member
엔티티의 username
필드명을 nickname
으로 변경한다면, 우리는 프로젝트 전체에서 이 필드를 사용한 모든 JPQL 문자열을 수동으로 찾아 수정해야 합니다.
IDE의 리팩토링 기능이 전혀 도와주지 못합니다.if
문으로 문자열을 조립해야 합니다.
이 과정은 매우 번거롭고, 코드를 지저분하게 만들며, 버그를 유발하기 쉽습니다.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"
// ...
}
annotationProcessor
(APT)가 우리가 만든 엔티티(@Entity
) 클래스들을 분석하여, 쿼리를 위한 메타 모델 클래스인 Member
라는 엔티티가 있다면, QMember
라는 클래스가 generated
폴더 아래에 만들어집니다.
이 QMember
클래스는 Member
엔티티의 구조를 그대로 따라가며, 쿼리를 위한 다양한 메서드들을 제공합니다.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(); // 결과가 하나일 때 사용
}
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(); // 결과가 여러 개일 때 사용
}
가장 큰 이유는타입 안전성(Type-Safety)을 확보하기 위함입니다. JPQL은 문자열 기반이므로 오타나 문법 오류를 컴파일 시점에 잡을 수 없고, 이는 런타임 에러로 이어질 수 있습니다. 반면, QueryDSL은 모든 쿼리를 자바 코드로 작성하기 때문에, 컴파일러가 오류를 즉시 잡아주어 애플리케이션의 안정성을 크게 높일 수 있습니다. 또한, IDE의 자동 완성 및 리팩토링 지원을 받을 수 있어 생산성도 향상됩니다.
Q-Type 클래스는컴파일 시점에 어노테이션 프로세서(APT)가하는 쿼리용 메타 모델입니다. 이 Q-Type 클래스는 원본 엔티티의 필드들을 그대로 가지고 있으며, 각 필드에 대해@Entity
어노테이션이 붙은 엔티티 클래스를 분석하여 자동으로 생성eq
,contains
,goe
와 같은 다양한 쿼리 메서드를 제공합니다. 개발자는 이 Q-Type을 사용하여, 마치 일반적인 자바 객체를 다루듯이 안전하고 직관적으로 쿼리를 작성할 수 있습니다.