정적 타입(컴파일 시 타입에 대한 정보를 결정)을 이용해서 SQL과 같은 쿼리를 생성할 수 있도록 해주는 오픈소스 프레임워크
SQL, JPQL을 코드로 작성할 수 있도록 해주는 빌더 API
Entity 클래스와 매핑되는 QClass 객체를 사용해서 쿼리 실행
✔️ QClass
- Entity와 형태가 같은 Static Class
- QueryDsl은 컴파일 단계에서 엔티티를 기반으로 QClass를 생성함
JPAAnnotationProcessor가 컴파일 시점에 작동해서 @Entity 등의 애너테이션을 찾아 해당 파일들을 분석해서 QClass를 만듦
실제로 Query를 사람이 짜다보면 수많은 쿼리를 수작업으로 생성해야하는데 사람이 하다보면 실수가 있기 마련이고,
JPQL과 같은 쿼리를 사용했을 경우 문자열로 처리되기 때문에 컴파일 단계가 아닌 런타임 단계에서야 오류를 발견할 수 있다.
⠀
오타 확률이 적어짐
➜ QueryDsl을 사용하면 쿼리를 문자열이 아닌 코드를 통해 작성하기 때문
코드로 작성하기 때문에 컴파일 단계에서 오류를 빠르게 발견 가능
객체 지향적으로 개발 가능
복잡한 쿼리나 동적 쿼리 작성이 편리
쿼리 작성 시 제약 조건 등을 메서드 추출을 통해 재사용 가능
JPA에서 지원하는 다양한 쿼리 방법 중 가장 단순한 방법
테이블이 아닌 엔티티 객체를 대상으로 검색하는 객체지향 쿼리
( SQL의 경우 DB 테이블을 대상으로 쿼리를 질의 )
SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않음
실제 실행 쿼리에선 결국 SQL로 변환됨
쿼리를 여전히 문자열로 입력
➜ 오타가 발생하거나 관리하는데 있어서 어려움이 따르고, type-check 불가능
( type-check - 타입 오류가 발생할 수 있는지 체크하는 것 )
⠀
컴파일 단계에서 오류 확인이 불가능하고, 런타임에서 해당 쿼리가 실행되어야만 오류 발견 가능
➜ 테스트 코드를 짜면 불안을 덜 수 있긴 하지만, 실제 프로그램을 운영하면서 오류가 발생할 수도 있다는 부담이 너무 커짐
검색 불가능
➜ 검색을 할 경우에도 테이블이 아닌 엔티티 객체를 대상으로 검색하기 때문에 모든 DB를 객체를 변환해서 검색이 불가능!
결국 검색 조건이 포함된 SQL이 필요하여 SQL로 변환됨
--> 객체지향 SQL
여기서 그랬음 다시 한번 찾아봐
✔️ 예시 코드를 통한 JPQL과 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();
[참고] https://velog.io/@soyeon207/QueryDSL-%EC%9D%B4%EB%9E%80
( spring data jpa와 함께 사용하는 경우이다 )
① build.gradle
에 설정 추가
참고로 여기서 gradlew로 하면 실행이 안된다고 하는데 다시 살펴보기
( 다른 설정은 제외한 QueryDsl 설정만 넣었습니다. )
// queryDsl Settings
buildscript {
ext {
queryDslVersion = "5.0.0"
}
}
plugins {
// queryDsl Settings
id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
.
.
}
dependencies {
// queryDsl Settings
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
// queryDsl Spatial Settings
implementation "com.querydsl:querydsl-spatial:${queryDslVersion}"
.
.
}
// QueryDsl 빌드 옵션 (선택사항)
def querydslDir = "$buildDir/generated/'querydsl'"
// -> `querydslDir` 옵션은 QClass들을 빌드 폴더 안으로 넣어 빌드되게 할 수 있어서 특히 유용!
// JPA 사용여부 및 사용 경로 설정
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
// build시 사용할 sourceSet 추가 설정
sourceSets {
main.java.srcDir querydslDir
}
// querydsl 컴파일 시 사용할 옵션 설정
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
}
// querydsl이 compileClassPath를 상속하도록 설정
configurations {
compileOnly {
extendsFrom annotationProcessor
}
querydsl.extendsFrom compileClasspath
}
② QueryDsl Config
설정
➜ QueryDsl이 query를 생성할 수 있도록 EntityManager를 주입하는 과정
➜ 여기서 등록한 jpaQueryFactory 빈을 Repository에서 사용하게 됨
@Configuration
public class QuerydslConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory(){
return new JPAQueryFactory(entityManager);
}
}
③ Entity
만들기
❗ 여기까지 하면 컴파일 단계에서 Entity를 기반으로 QClass 파일들을 생성함!
이 QClass를 기반으로 쿼리를 작성하게 된다.
④ 실제 쿼리를 작성하고 수행할 Repository
작성
❗ 나의 경우는 OCP(개방-폐쇠의 원칙)를 지키기 위해 인터페이스와 구현체(impl)를 나누어 작성하였다.
YataRepository
YataRepositoryImpl
implements YataRepository
YataRepositoryImpl
클래스 내에서 QueryDsl을 사용한 구체적인 코드