본문 바로가기

Spring

QueryDSL이란?

이번 글에서는 저가 계속해서 공부 해봐야지, 알아봐야지 했다가 미루고 미뤘던 QueryDSL에 대해 이것은 무엇인지 또 어디서 사용되는 지 그리고 오랜만에 에러를 만나 어떻게 해결하려고 노력했는 지에 대해 작성해보겠습니다

QueryDSL이란?

QueryDSL이란 단어 그대로 쿼리 생성에 특화된 프레임워크를 의미합니다

여기서 JPA에 대해 공부해보신 분들 또는 제 스크럼을 열심히 들으신 분들은 두 가지 의문점이 들 수 있습니다

첫번째로는 ‘JPA가 자동으로 SQL 쿼리문을 생성해주는 것 아닌가?’

맞습니다 JPA는 객체 지향적 언어 또는 어플리케이션과 관계형 DB 사이의 패러다임 불일치를 해결해 주는 프레임워크입니다 개발자는 객체지향관점으로 개발하고 JPA는 자동으로 SQL 쿼리문을 생성합니다 자동으로 SQL 쿼리문을 생성해주기 때문에 개발자는 SQL 관점 프로그래밍을 하지 않아도 됩니다

그러나 아무리 JPA가 잘 만들어지고 두 관점의 간격을 좁혀준다고 해도 콩고기가 육고기가 될 수 없듯이 완전한 분리는 불가능합니다 완전한 분리가 불가능하다는 뜻은 복잡한 쿼리 생성을 JPA에게 맡길 수 없고 개발자가 수동으로 프로그래밍해야 한다는 뜻입니다

‘이 내용은 알고 있던 사실인데? 그러니까 JPQL이 있는거 아냐?’라고 두번째 의문점이 들 수 있습니다

JPQL는 JPA에 종속되어 있는 객체지향적 관점을 가진 SQL문입니다 하지만 JPQL은 문제점을 가지고 있습니다 근본적인 문제점은 문자열이란 것입니다

JPQL은 문자열로 이루어져있기 때문에 컴파일 단계에서 오류를 확인하지 못합니다 그래서 런타임 중에 메소드가 호출되어 JPQL이 파싱되어야 문법 오류를 발견할 수 있습니다 또한 직관적인 동적 쿼리 작성이 어렵다는 단점도 가지고 있습니다

QueryDSL은 이러한 JPQL의 단점을 보완하고 실무에서 JPA와 연계되어, 가장 많이 사용되는 프레임워크 중 하나입니다

//jpql 코드 예시
String jpql = "select * from Member m join Point p on p.member_id = m.id"
List<Member> result = em.createQuery(jpql, Member.class).getResultList();
//QueryDSL 코드 예시
return jpaQueryFactory
.from(member)
.join(member.point, point)
.fetch();

QueryDSL 사용 방법

QueryDSL은 JPA와 연계되서 사용되지만 JPA와 분리되어 있는 프레임워크입니다

따라서 dependency를 따로 추가해줘야합니다

두 가지 방법이 있습니다 하나는 플러그인을 사용하는 방법과 직접 dependency를 gradle 파일에 추가하는 방법이 있는데요

저는 플러그인을 사용하는 방법을 추천하지 않습니다 왜냐하면 많이 사용하는 플러그인 깃허브를 확인해보시면 업데이트가 최소 3년 전으로 현재 버전과 맞지 않는 부분이 많습니다 QueryDSL은 그동안 꽤 많은 버전 업데이트가 되었구요 그래서 다소 줄이 길어지더라도 직접 셋업을 하는 방식을 추천합니다

저가 설정한 dependency 셋업은 다음과 같습니다 spring 버전은 ‘2.7.0’입니다

dependencies {
   ...

    // queryDSL 설정
    implementation "com.querydsl:querydsl-jpa"
    implementation "com.querydsl:querydsl-core"
    implementation "com.querydsl:querydsl-collections"
    annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa" // querydsl JPAAnnotationProcessor 사용 지정
    annotationProcessor "jakarta.annotation:jakarta.annotation-api" // java.lang.NoClassDefFoundError (javax.annotation.Generated) 대응 코드
    annotationProcessor "jakarta.persistence:jakarta.persistence-api" // java.lang.NoClassDefFoundError (javax.annotation.Entity) 대응 코드
}

tasks.named('test') {
    useJUnitPlatform()
}

// Querydsl 설정부
def generated = 'src/main/generated'

// querydsl QClass 파일 생성 위치를 지정
tasks.withType(JavaCompile) {
    options.getGeneratedSourceOutputDirectory().set(file(generated))
}

// java source set 에 querydsl QClass 위치 추가
sourceSets {
    main.java.srcDirs += [ generated ]
}

// gradle clean 시에 QClass 디렉토리 삭제
clean {
    delete file(generated)
}
@RepositoryRestResource
public interface ArticleCommentRepository extends
        JpaRepository<ArticleComment, Long>,
        QuerydslPredicateExecutor<ArticleComment>,
        QuerydslBinderCustomizer<QArticleComment> {
}

환경 설정 후 에러

위와 같이 환경 설정 후 gradle build를 하니 다음과 같이 에러가 발생했습니다

처음엔 당연히 build시에 오류가 났기 때문에 gradle 문제인줄 알았습니다 그래서 구글링해보니 spring 3 이상 부터는 dependency가 아래와 같이 변경되었습니다

implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api" // java.lang.NoClassDefFoundError (javax.annotation.Generated) 대응 코드
annotationProcessor "jakarta.persistence:jakarta.persistence-api" // java.lang.NoClassDefFoundError (javax.annotation.Entity) 대응 코드

‘아 이제 되겠지’ 라고 생각했지만 똑같은 문제가 발생했습니다

그래서 gradle이 지금 꼬여서 문제가 발생했나 싶어서 프로젝트 gradle 자체를 Refresh Gradle Denpendencies합니다

‘아 이제 되겠지’라고 생각했지만 똑같은 문제가 발생했습니다..

스프링 버전이 문제인가 생각해서 버전을 2.7.0으로 내리고 dependency 또한 그에 맞게 변경했습니다

‘아 이제 되겠지’라고 생각했지만 똑같은 문제가 발생했습니다…

오류 코드를 자세히 확인해보니 main test에서 contextLoads에 대한 FALIED이 발생하는 것을 확인할 수 있었습니다

그럼 build 시 발생하는 오류가 아니라 app 구동 자체에 에러가 발생한 것이 아닐까 싶어서 main으로 가서 app을 실행시켜봤습니다 그랬더니 오류가 다음과 같이 자세히 나왔습니다

차근차근 읽어보니 다음의 구문을 발견할 수 있었습니다

자세히 확인하면 다음과 같습니다

Could not create query for public abstract void org.springframework.data.querydsl.binding.QuerydslBinderCustomizer.customize(org.springframework.data.querydsl.binding.QuerydslBindings,com.querydsl.core.types.EntityPath)!

Reason: Failed to create query for method public abstract void org.springframework.data.querydsl.binding.QuerydslBinderCustomizer.customize(org.springframework.data.querydsl.binding.QuerydslBindings,com.querydsl.core.types.EntityPath)!

QuerydslBinderCustomizer 에 customize 추상 메서드가 만들어지지 않았다…?

바로 QuerydslBinderCustomizer 를 확인해봤습니다

하하… QuerydslBinderCustomizer 인터페이스에 추상 메소드 customize가 존재했습니다

이 메소드를 상속받은 Repository에서 오버라이딩하지 않아서 발생한 에러였습니다

그래서 다음과 같이 Repository에서 오버라이딩하여 구현했습니다.

AritcleRepository도 interface지만 자바8 이상부터는 인터페이스에서도 추상 메서드를 구현할 수 있도록 했기 때문에 default를 붙여서 customize를 생성했습니다

이렇게 에러를 해결할 수 있었습니다

여러분들도 에러가 발생하면 app 구동을 통해 자세한 에러 내역을 꼼꼼히 확인해보시고 새로 사용하는 class 또는 interface에 대해 시간 날때 한번씩 보시는 것을 추천드립니다 저희도 모르는 유용한 메소드가 숨겨져 있을 수 있기 때문이죠

아직도 왜 컴파일 단계에서 오버라이딩하지 않았다고 말해주지 않았는지 의문입니다 그것만 말해줬다면 삽질하지 않았는데..

QueryDSL은 보통 어디에 사용될까?

근본적으로 QueryDSL은 동적으로 직관적인 쿼리문을 작성하기 위해 사용됩니다

그렇기에 개발 서비스 상황에 따라 복잡한 쿼리문을 생성하기 위해서 사용이 되곤 하는데요

일반적인 상황을 가정했을 때 자주 사용되는 상황은 바로 검색 쿼리문 작성입니다

예시로 좀 더 이해하기 쉽게 설명드리겠습니다

QuerydslPredicateExecutor method는 모든 필드에 대한 검색 쿼리문을 자동 생성해줍니다

다음은 data-rest를 통해 자동으로 생성된 api 주소를 HAL Explorer를 통해 확인하는 모습입니다

Table의 내용은 Mockaroo를 통해 자동 생성한 데이터를 삽입했습니다

위의 내용은 다음에 시간이 있을 때 더 자세히 말씀드리겠습니다

title 필드에 sit 이라는 문자열이 담겨있는지 확인하기 위해서 다음의 내용을 검색할 수 있습니다

http://localhost:8080/api/articles?title=sit”

검색하면 다음과 같이 sit 문자열이 title 필드에 담긴 내용만 확인할 수 있습니다

하지만 다음과 같은 방식은 문제점을 가지고 있습니다

대소문자와 부분검색을 지원하지 않는다는 점입니다

이 부분은 사용자가 직접 custom해야하는데 아까 본 custom method가 이러한 기능을 지원합니다

다음과 같이 설정해주면 대소문자와 부분 검색을 통한 검색을 할 수 있습니다

likeIgnoreCase 와 ContainsIgnoreCase 에 대해선 궁금하시면 찾아보시는 것을 추천드립니다

짧게 설명하면 likeIgnoreCase 은 부분 검색 시 수동으로 %를 삽입해야하고

ContainsIgnoreCase 는 like ‘%${v}%’ 다음과 같은 방식으로 자동으로 %를 삽입해줍니다

이렇게 단순한 검색 쿼리문 작성 말고도 QueryDSL은 많은 기능에 활용될 수 있는 유용한 프레임워크입니다

저가 설명한 부분 말고도 많은 기능들이 이 프레임워크에 포함되어 있을 거라 생각합니다

혹시나 관심이 있으신 분들은 어떤 서비스에 활용되고 어떻게 사용되는지 알아보셔도 좋을 것 같네요

혹시 알아보시고 정보도 공유해주세요😊

긴 글 읽어주셔서 감사합니다!

'Spring' 카테고리의 다른 글

Spring Batch란?  (0) 2026.01.18
Swagger UI란?  (0) 2026.01.18
feign 적용기  (0) 2026.01.18
양방향 연관관계와 진행하면서 만난 문제점  (0) 2026.01.18
JPA란?  (0) 2026.01.18