공부할 것 !

[JPA] QueryDSL

sejin2 2024. 4. 13. 19:34

Spring Data JPA는 Java 오브젝트를 관계형 데이터베이스에 매핑하는 Java 표준이다. 
JPA는 개발자가 SQL문을 작성할 필요 없이 객체를 조작하는 것만으로 데이터베이스에 CRUD를 수행할 수 있게 해준다.
대표적인 JPA 기반 구현체로 Hibernate가 있다. 
Spring Data JPA에서 제공하는 JPARepository와 같은 인터페이스를 상속받아 새로운 Repository 인터페이스를 생성하고, 정해진 규칙대로 메서드명을 명명하면 Spring Data JPA가 자동으로 코드를 생성한다.

SpringBoot JPA( ORM ) 에서 제공하는 인터페이스 형태의 쿼리 메서드를 사용해도 기본적인 쿼리를 하기에는 충분하지만,  페이징이나 복잡한 쿼리를 작성해야하는 경우에는 구현이 어렵다. 
이렇게 복잡한 쿼리를 작성하기 위해서 JPA는 JPQL을 지원한다. 
그러나, JPQL에는 크게 두 가지의 문제점이 있다.
1) JPQL은 문자열이기 때문에 문법이 틀려도 개발자는 이를 알아차리지 못하고, 런타임 중에 메서드가 호출되어 JPQL이 호출되어야만 문법오류를 발견할 수 있다. @Query 어노테이션을 사용하면 문법 오류를 발견할 수 있지만 이도 프로그램을 실행해야만 알 수 있다. 즉, JPQL을 파싱하는 프로세스가 동작을 해야만 오류를 발견할 수 있고, 컴파일 과정에서는 알 수 없다는 문제점이 있다.
2) JPQL은 문자열이기 때문에 직관적인 동적 쿼리 작성이 어렵다. 동적 쿼리를 작성하기 위해서는 문자열을 조작하는 방식으로 로직을 구성해야하므로 코드의 직관성이 떨어진다.

이러한 이유로 QueryDSL을 사용하는데, 이는 JPA가 지원하는 표준 기술은 아니지만 위의 두가지 문제점을 보완해준다.
QueryDSL은 하이버네이트 쿼리 언어의 쿼리를 타입에 안전하게 생성 및 관리해주는 즉, 쿼리 생성에 특화된 프레임워크 정적 타입을 이용하여 SQL과 같은 쿼리를 생성할 수 있게 해준다.

QueryDSL의 동작원리

  1. 타입 세이프 쿼리 작성: 개발자는 자바 코드를 사용하여 데이터베이스 쿼리를 작성한다. QueryDSL은 타입 세이프 API를 제공하여, 컴파일 시점에 쿼리의 정확성을 검증할 수 있게 한다. 이로써 런타임에 발생할 수 있는 많은 오류를 예방할 수 있다.
  2. 쿼리 변환: 작성된 자바 코드는 QueryDSL 프레임워크에 의해 SQL 쿼리로 변환된다. 이 변환 과정은 데이터베이스 유형에 따라 최적화되어, 다양한 데이터베이스 시스템에서 효율적으로 작동한다.
  3. 쿼리 실행: 변환된 SQL 쿼리는 데이터베이스에 전송되어 실행된다. QueryDSL은 실행 결과를 자바 객체로 매핑하여 반환, 개발자가 쉽게 결과를 활용할 수 있도록 한다.
  4. 결과 처리: 데이터베이스에서 반환된 결과는 QueryDSL을 통해 자바 객체로 매핑되고, 이를 통해 개발자는 결과 데이터를 안전하고 편리하게 처리할 수 있다.

 

QueryDSL을 사용하기 위해 build.gradle에 의존성 추가

 // QueryDsl
    implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
    annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
    annotationProcessor "jakarta.annotation:jakarta.annotation-api"
    annotationProcessor "jakarta.persistence:jakarta.persistence-api"

빌드 추가 옵션

// QueryDsl 빌드 옵션 (선택)
def querydslDir = "$buildDir/generated/querydsl"

sourceSets {
    main.java.srcDirs += [ querydslDir ]
}

tasks.withType(JavaCompile) {
    options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
}

clean.doLast {
    file(querydslDir).deleteDir()
}

- 빌드 디렉토리 설정 : QueryDSL에서 생성된 소스 파일들을 저장할 디렉토리를 정의한다. 
- 소스 디렉토리 추가 : QueryDSL에 의해 생성된 소스 코드가 포함된 디렉토리를 메인 소스 세트에 추가한다.
그러면 Gradle이 이 디렉토리 내의 코드도 컴파일 과정에 포함시킬 수 있다.
- 어노테이션 프로세서 설정 : QueryDSL은 어노테이션 프로세서를 사용해 쿼리 타입을 자동으로 생성한다.
이 설정을 통해 어노테이션 프로세서가 생성한 소스 코드의 저장 위치를 지정해 소스 코드의 관리가 용이해진다.
- 클린 태스크 설정 : 빌드 시 clean 태스크가 실행될 때 querydslDir 디렉토리를 삭제하도록 설정한다.
그러면 빌드 환경을 깨끗하게 유지할 수 있으며, 불필요한 생성 코드가 프로젝트에 남아있지 않도록 한다.

QueryDSL을 사용하기 위한 설정 클래스 생성

@Configuration
public class QueryDslConfig {
    @PersistenceContext
    private EntityManager entityManager;
    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}


이렇게 설정을 해놓으면 QueryDSL 빌드 과정 중에 어노테이션 프로세서가 작동하여 JPA 엔티티 등의 소스코드를 분석하고, 그에 해당하는 Q클래스( 메타 모델 )를 생성한다.

이러한 클래스들은 실제 엔티티를 기반으로 생성된 엔티티의 구조를 반영하는 메타 모델이다. 
이 클래스들은 각 필드에 해당하는 정적 메타데이터를 가지고 있어서, 타입 세이프한 쿼리를 작성할 때 사용된다.
이렇게 생성된 클래스들을 사용하여 QueryDSL 쿼리를 작성할 때 각 필드에 직접 접근할 수 있다.
데이터베이스의 스키마가 변경되어 엔티티 클래스가 업데이트 되면, 빌드 프로세스가 다시 실행되면서 이 메타모델 클래스들도 갱신되어 최신의 엔티티 구조를 반영하게 된다.