[SpringBoot] QueryDSL 사용해보기

손경이·2024년 5월 7일
0

2024.05.04
[책 - 스프링 부트 핵심 가이드]
환경 - 스프링부트 2.5.6, 자바 JDK11, Gradle 프로젝트
스프링 부트 핵심 가이드 QueryDSL 연습 깃허브


💡 QueryDSL : JPA 엔티티와 관련된 쿼리를 작성할 수 있게 해주는 라이브러리

  • Querydsl은 엔티티 클래스를 기반으로 쿼리 클래스를 생성합니다.
  • 쿼리 클래스는 Querydsl에서 제공하는 Q 타입으로, 엔티티의 속성들을 정적으로 참조할 수 있게 해줍니다.
  • Q 클래스들은 일반적으로 'Q'로 시작하며, 엔티티 클래스의 이름을 뒤따라서 생성됩니다.
  • 보통 'src/main/generated' 또는 'build/generated'와 같은 디렉토리에 생성됩니다.

    QueryDSL 이란?


1. Gradle 빌드 스크립트인 build.gradle 코드


  • build.gradle 파일을 사용하면 QueryDSL을 사용하여 JPA 엔티티들을 손쉽게 쿼리 작성할 수 있습니다.
  • plugins에 추가
plugins {
    // QueryDSL 에서 JPAAnnotationProcessor 사용을 위한 APT 플러그인 추가
    id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10'
}
  • dependencies에 QueryDSL 의존성 추가
dependencies {
    /*
    QueryDSL 관련 의존성 추가
    querydsl-jpa: QueryDSL JPA 라이브러리
    querydsl-apt: 쿼리 타입(Q클래스)을 생성할 때 필요한 라이브러리

    apt(annotation processing tool)
    - 어노테이션으로 정의된 코드를 기반으로 새로운 코드를 생성하는 기능
     */
    implementation 'com.querydsl:querydsl-jpa'
    implementation 'com.querydsl:querydsl-apt'
}
  • QueryDSL 설정
/* 
에러 Unable to load class ''. 가 생겨서 아래 추가
*/
// QueryDSL이 생성한 소스 코드의 디렉토리를 지정하는 변수, 기본적으로 'src/main/generated'로 설정
def querydslSrcDir = 'src/main/generated'

// QueryDSL 플러그인의 설정을 정의
querydsl {
	// 사용할 QueryDSL 라이브러리를 지정
    library = "com.querydsl:querydsl-apt"
    //  JPA를 사용할 것인지 여부를 나타내며, 여기서는 true로 설정되어 있어 JPA를 사용
    jpa = true
    // QueryDSL이 생성한 소스 코드를 저장할 디렉토리를 지정
    querydslSourcesDir = querydslSrcDir
}

/* 
에러 Unable to load class 'com.querydsl.apt.jpa.JPAAnnotationProcessor' 가 생겨서 아래 추가
*/
// 의존성 구성(configurations)을 정의
configurations {
	// QueryDSL의 의존성을 컴파일 클래스패스에 추가
    querydsl.extendsFrom compileClasspath
    // developmentOnly 및 runtimeClasspath에 대한 설정
    developmentOnly
    runtimeClasspath {
        extendsFrom developmentOnly
    }
}

// 소스 세트(source set)를 구성
// main 소스 세트에 QueryDSL이 생성한 소스 코드 디렉토리를 추가
sourceSets {
    main {
        java {
            srcDirs = ['src/main/java', querydslSrcDir]
        }
    }
}

// QueryDSL을 컴파일하는 옵션을 정의
compileQuerydsl{
	// 어노테이션 프로세서 경로를 설정합니다. 여기서는 QueryDSL의 configurations.querydsl을 사용
    options.annotationProcessorPath = configurations.querydsl
}

2. build.gradle 설정을 해주고 설정 reload 해줍니다.

.gitignore에 추가

  • Git이 해당 디렉토리의 파일을 추적하지 않도록 설정합니다.
  • 보통 generated 또는 build와 같은 디렉토리는 소스 코드의 생성물이나 빌드 과정에서 생성되는 파일들을 담는 공간입니다. 이러한 파일들은 소스 코드 저장소에 포함시키지 않는 것이 일반적입니다.
### Custom ###
src/main/generated/

3. 기본적인 QueryDSL 사용하기

  • QueryDSL에 의해 생성된 Q도메인 클래스를 활용하는 코드

가. JPAQuery 객체를 사용해서 코드를 작성하는 방법

    @PersistenceContext
    EntityManager entityManager;

    @Test
    @DisplayName("JPAQuery를 활용한 QueryDSL 테스트 코드")
    void queryDslTest() {
        // QueryDSL을 사용하기 위해서 JPAQuery 객체를 사용 - JPAQuery는 엔티티 매니저(entityManager)를 활용해 생성
        JPAQuery<Product> query = new JPAQuery<>(entityManager);
        QProduct qProduct = QProduct.product;

        // JPAQuery는 빌더 형식으로 쿼리 작성
        List<Product> productList = query
                .from(qProduct)
                .where(qProduct.name.eq("펜"))
                .orderBy(qProduct.price.asc())
                .fetch();
        // List 타입으로 값을 리턴받기 위해서 fetch() 메서드 사용
        // - 4.0.1 이전 버전의 QueryDSL을 설정한다면 list() 메서드 사용

        for (Product product : productList) {
            System.out.println("--------------------");
            System.out.println();
            System.out.println("Product Number" + product.getNumber());
            System.out.println("Product Name" + product.getName());
            System.out.println("Product Price" + product.getPrice());
            System.out.println("Product Stock" + product.getStock());
            System.out.println();
            System.out.println("--------------------");
        }
    }

나. JPAQueryFactory를 활용해 쿼리를 작성

  • JPAQuery를 사용했을 때와 달리 JPAQueryFactory는 select 절부터 작성 가능
	@Test
    @DisplayName("JPAQueryFactory를 활용한 QueryDSL 테스트 코드")
    void queryDslTest2() {
        JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
        QProduct qProduct = QProduct.product;

        List<Product> productList = jpaQueryFactory
                .selectFrom(qProduct)
                .where(qProduct.name.eq("펜"))
                .orderBy(qProduct.price.asc())
                .fetch();

        for (Product product : productList) {
            System.out.println("--------------------");
            System.out.println();
            System.out.println("Product Number" + product.getNumber());
            System.out.println("Product Name" + product.getName());
            System.out.println("Product Price" + product.getPrice());
            System.out.println("Product Stock" + product.getStock());
            System.out.println();
            System.out.println("--------------------");
        }
    }
  • 전체 칼럼을 조회하지 않고 일부만 조회하고 싶다면 selectFrom()이 아닌
    select()와 from() 메서드를 구분해서 사용하면 됨
	@Test
    @DisplayName("JPAQueryFactory를 활용한 QueryDSL 테스트 코드")
    void queryDslTest3() {
        JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
        QProduct qProduct = QProduct.product;

        List<String> productList = jpaQueryFactory
                .select(qProduct.name)
                .from(qProduct)
                .where(qProduct.name.eq("펜"))
                .orderBy(qProduct.price.asc())
                .fetch();

        for (String product : productList) {
            System.out.println("--------------------");
            System.out.println("Product Name" + product);
            System.out.println("--------------------");
        }

        List<Tuple> tupleList = jpaQueryFactory
                .select(qProduct.name, qProduct.price)
                .from(qProduct)
                .where(qProduct.name.eq("펜"))
                .orderBy(qProduct.price.asc())
                .fetch();

        for (Tuple product : tupleList) {
            System.out.println("--------------------");
            System.out.println("Product Name" + product.get(qProduct.name));
            System.out.println("Product Price" + product.get(qProduct.price));
            System.out.println("--------------------");
        }
    }

4. JPAQueryFactory 빈을 활용한 테스트 코드

  • QueryDSL 컨피그 파일 생성
/**
 * QueryDSL 컨피그 파일 생성
 */
@Configuration
public class QueryDSLConfiguration {
    @PersistenceContext
    EntityManager entityManager;

    // JPAQueryFactory 객체를 @Bean 객체로 등록해두면 매번 JPAQueryFactory를 초기화하지 않고 스프링 컨테이너에서 가져다 쓸 수 있음
    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}
  • JPAQueryFactory 빈을 활용한 테스트 코드
	@Autowired
    JPAQueryFactory jpaQueryFactory;

    @Test
    @DisplayName("JPAQueryFactory 빈을 활용한 테스트 코드")
    void queryDslTest4() {
        QProduct qProduct = QProduct.product;

        List<String> productList = jpaQueryFactory
                .select(qProduct.name)
                .from(qProduct)
                .where(qProduct.name.eq("펜"))
                .orderBy(qProduct.price.asc())
                .fetch();

        for (String product : productList) {
            System.out.println("--------------------");
            System.out.println("Product Name" + product);
            System.out.println("--------------------");
        }
    }

쿼리 반환 메서드 참고

  • List fetch() : 조회 결과를 리스트로 반환
  • T fetchOne() : 단 건의 조회 결과를 반환
  • T fetchFirst() : 여러 건의 조회 결과 중 1건을 반환
    • 내부 로직을 살펴보면 '.limit(1).fetchOne()으로 구현돼 있음
  • Long fetchCount() : 조회 결과의 개수를 반환
  • QueryResult fetchResults() : 조회 결과 리스트와 개수를 포함한 QueryResults를 반환
// 예시
List<String> productList = jpaQueryFactory
                .select(qProduct.name)
                .from(qProduct)
                .where(qProduct.name.eq("펜"))
                .orderBy(qProduct.price.asc())
                .fetch();

참고

0개의 댓글