[Project] QueryDsl의 사용과 프로젝트에 적용하기 !

현주·2023년 4월 29일
0
post-custom-banner

✏️ QueryDsl

  • 정적 타입(컴파일 시 타입에 대한 정보를 결정)을 이용해서 SQL과 같은 쿼리를 생성할 수 있도록 해주는 오픈소스 프레임워크

  • SQL, JPQL을 코드로 작성할 수 있도록 해주는 빌더 API

  • Entity 클래스와 매핑되는 QClass 객체를 사용해서 쿼리 실행

✔️ QClass

  • Entity와 형태가 같은 Static Class
  • QueryDsl은 컴파일 단계에서 엔티티를 기반으로 QClass를 생성함
    JPAAnnotationProcessor가 컴파일 시점에 작동해서 @Entity 등의 애너테이션을 찾아 해당 파일들을 분석해서 QClass를 만듦

공식 사이트

💡 왜 QueryDsl을 사용할까?

실제로 Query를 사람이 짜다보면 수많은 쿼리를 수작업으로 생성해야하는데 사람이 하다보면 실수가 있기 마련이고,
JPQL과 같은 쿼리를 사용했을 경우 문자열로 처리되기 때문에 컴파일 단계가 아닌 런타임 단계에서야 오류를 발견할 수 있다.

  • 오타 확률이 적어짐
    ➜ QueryDsl을 사용하면 쿼리를 문자열이 아닌 코드를 통해 작성하기 때문

  • 코드로 작성하기 때문에 컴파일 단계에서 오류를 빠르게 발견 가능

  • 객체 지향적으로 개발 가능

  • 복잡한 쿼리나 동적 쿼리 작성이 편리

  • 쿼리 작성 시 제약 조건 등을 메서드 추출을 통해 재사용 가능


✏️ JPQL

  • JPA에서 지원하는 다양한 쿼리 방법 중 가장 단순한 방법

  • 테이블이 아닌 엔티티 객체를 대상으로 검색하는 객체지향 쿼리
    ( SQL의 경우 DB 테이블을 대상으로 쿼리를 질의 )

  • SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않음

  • 실제 실행 쿼리에선 결국 SQL로 변환됨

✔ jPQL의 문제점

  • 쿼리를 여전히 문자열로 입력
    ➜ 오타가 발생하거나 관리하는데 있어서 어려움이 따르고, 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


🌼 프로젝트에 적용한 QueryDsl 사용법

( 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을 사용한 구체적인 코드


이어지는 내용 👉 [Project] fetchJoin()으로 JPA N+1 문제 해결하기 !

post-custom-banner

0개의 댓글