본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.
QueryDSL JPA
QueryDSL JPA는
- SQL, JPQL을 코드로 작성할 수 있도록 해주는 빌더 API이고
- Entity 클래스와 매핑되는 QClass라는 객체를 사용해서 쿼리를 실행한다
QClass란?
QueryDSL 은 컴파일 단계에서 엔티티를 기반으로 QClass 를 생성하는데 JPAAnnotationProcessor 가 컴파일 시점에 작동해서 @Entity 등등의 어노테이션을 찾아 해당 파일들을 분석해서 QClass 를 만든다.
QClass 는 Entity 와 형태가 똑같은 Static Class 이다. QueryDSL 은 쿼리를 작성할 때 QClass 를 기반으로 쿼리를 실행한다.
JPQL이란?
JPA 에서 지원하는 다양한 쿼리 방법 중 가장 단순한 조회 방법으로, SQL 의 경우에는 DB 테이블을 대상으로 쿼리를 질의하지만, JPQL 은 엔티티 객체를 대상으로 쿼리를 질의한다.
EX)
String jpql = “select m From Member m where m.name like ‘%hello%’”;
List<Member> result = em.createQuery(jpql, Member.class).getResultList();
(SQL 로 변환시) select * from members where name like ‘%hello%’;
위 설명만 봤을 때에는 굳이 QueryDSL JPA 를 사용해야 하나 ? 그냥 JPQL 을 사용하면 안되나 ? 라는 생각이 들수 있다.
하지만, JPQL 에는 문제점이 존재한다.
JPQL의 문제점
- 쿼리를 여전히 문자열로 입력한다. ⇒ 오타가 발생하거나 관리하는데 있어서 어려움이 따르고, type-check 가 불가능하다
- 컴파일 단계에서 오류를 확인할 수 없고, 런타임에서 해당 쿼리가 실행되어야 오류를 발견할 수 있다. ⇒ 테스트 코드를 짜면 불안을 덜 수 있긴 하지만, 실제 프로그램을 운영하면서 오류가 발생할 수도 있다는 부담이 너무 커진다.
QueryDSL JPA 는 JPQL 이 가지고 있는 문제점들을 해결해준다.
QueryDSL JPA를 사용해야 하는 이유
- 쿼리를 여전히 문자열로 입력하기 때문에 오타가 발생하거나 관리하기 어렵다. ⇒ QueryDSL 은 쿼리를 문자열로서가 아니라 코드를 통해서 작성하기 때문에 오타가 날 확률이 적어지고, 객체 지향적으로 개발할 수 있다.
- 컴파일 단계에서 오류를 확인할 수 없고, 런타임 시 해당 쿼리가 실행되어야지만 오류를 확인할 수 있다. ⇒ QueryDSL 은 코드로서 작성하기 때문에 컴파일 단계에서도 오류를 빠르게 발견할 수 있다.
예를 들어서
회원(member) 와 포인트(point) 를 조인해서 가져와야 할 때
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();
이런 식으로 코드를 사용해서 나타낼 수 있다.
오타가 나더라도 컴파일 단계에서 오류를 확인 할 수 있고, 코드로서 작성하기 때문에 더욱 객체 지향적으로 개발할 수 있다.
이러한 이유로 spring 에서 JPA 를 사용할 때 QueryDSL JPA 를 보통 함께 사용한다.
build.gradle에 QueryDSL 세팅
dependencies {
...
implementation 'com.querydsl:querydsl-jpa'
implementation 'com.querydsl:querydsl-apt'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
}
def querydslSrcDir = 'src/main/generated'
sourceSets {
main {
java {
srcDirs += [ querydslSrcDir ]
}
}
}
tasks.withType(JavaCompile) {
options.generatedSourceOutputDirectory = file(querydslSrcDir)
}
clean {
delete file(querydslSrcDir)
}
dependencies
implementation 'com.querydsl:querydsl-jpa'
implementation 'com.querydsl:querydsl-apt'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
implementation 'com.querydsl:querydsl-jpa'
→ QueryDSL 을 사용하기 위한 라이브러리
→ QuerydslRepositorySupport, QuerydslPredicateExecutor 등 QueryDSL 내부 클래스는 사용 가능하지만, 실제로 쿼리를 위해 사용되는 QClass 는 생성되지 않는다.
implementation 'com.querydsl:querydsl-apt'
→ QClass 를 생성하기 위한 라이브러리
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
→ QueryDSL JPA 의 경우에는 Q 클래스를 활용해서 동작하는데 @Entity 어노테이션을 선언한 클래스를 탐색하고, Q 클래스를 생성한다.
Q 클래스를 생성하는 방법에는 크게 두가지로 나뉜다.
- com.ewerk.gradle.plugins.querydsl
- 2018년 이후 업데이트 된 적이 없다.
- gradle 버전이 계속적으로 업데이트되면서 해당 플러그인 외 여러가지 설정해줘야 할 것 들이 많다.
- annotationProcessor
이번에는 ewerk 관련 단점들 때문에 두번째 방식인 annotationProcessor 를 사용한다.
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
이클립스 재단으로 자바가 이관되면서 java → jakarta 로 이름이 변경되었다.
Q 파일을 찾지 못해서 발생하는 오류인 java.lang.NoClassDefFoundError (javax.annotation.Entity / javax.annotation.Generated) 에러 발생 대응을 위한 코드 이다.
sourceSets
def querydslSrcDir = 'src/main/generated'
sourceSets {
main {
java {
srcDirs += [ querydslSrcDir ]
}
}
}
gradle build 시 QClass 소스도 함께 build 하기 위해서 sourceSets 에 해당 위치를 추가해준다.
tasks.withType
tasks.withType(JavaCompile) {
options.generatedSourceOutputDirectory = file(querydslSrcDir)
}
annotationProcessorGeneratedSourcesDirectory 를 사용할 수도 있는데,
Gradle 공식 문서에 해당 기능은 deprecated 되었고, 추후 major version 에선 삭제할 것이라고 소개하고 있기 때문에 generatedSourceOutputDirectory 를 사용한다.
generatedSourceOutputDirectory 는 annotation processors 에서 생성한 소스 파일을 저장할 디렉토리를 지정 해준다. (Gradle 공식문서 → CompileOptions - Gradle DSL Version 7.5.1 )
이 코드를 통해 위에서 선언한 querydslSrcDir 변수의 src/main/generated 에다가 annotation processors 가 만든 QClass 들을 저장해준다.
clean
clean {
// clean 실행 시 생성된 QClass 삭제
delete file(querydslSrcDir)
}
build clean 시에 생성되었던 QClass 를 모두 삭제 (querydslSrcDir = src/main/generated)
'패스트캠퍼스 강의' 카테고리의 다른 글
[22일차] 50일 포트폴리오 챌린지 (0) | 2023.08.29 |
---|---|
[21일차] 50일 포트폴리오 챌린지 (0) | 2023.08.28 |
[19일차] 50일 포트폴리오 챌린지 (0) | 2023.08.26 |
[18일차] 50일 포트폴리오 챌린지 (0) | 2023.08.25 |
[17일차] 50일 포트폴리오 챌린지 (0) | 2023.08.24 |