본문 바로가기
IT/Spring boot

[Spring boot] What is QueryDSL?

by kyu-nahc 2024. 9. 18.
반응형

 

QueryDSL

QueryDSL은 정적 타입을 이용해서 SQL과 같은 쿼리를 생성해 주는 오픈소스 프레임워크이다.
쿼리를 문자열로 작성하거나 작성하는 것이 아닌, 

QueryDSL이 제공하는 Fluent API를 이용해 코드 작성의 형식으로 쿼리를 생성할 수 있게 도와준다.

즉 QueryDSL은 SQL, JPQL 등을 코드로 작성할 수 있도록 해주는 빌더 오픈소스 프레임워크이다.

 

사실, QueryDSL 이 JPA에서만 사용하는 프레임워크로만 알 수도 있지만,

위의 공식 사이트를 보면 JPA 뿐만 아니라 SQL, Mongodb, Lucenece 등

다양한 언어에 대해서 서비스를 제공한다.

이번엔 JPA를 사용할 때 함께 많이 사용하는 QueryDSL JPA에 대해서 알아볼 것이다.

QueryDSL JPA는 SQL, JPQL을 코드로 작성할 수 있도록 해주는 빌더 API이고,
Entity 클래스와 매핑되는 QClass라는 객체를 사용해서 쿼리를 실행한다.

 

QueryDSL은 컴파일 단계에서 엔티티를 기반으로 QClass를 생성하는데

JPAAnnotationProcessor가 컴파일 시점에 작동해서

@Entity 어노테이션을 찾아 해당 파일들을 분석해서 QClass를 만든다.

QClass는 Entity와 형태가 똑같은 Static Class이다.
QueryDSL은 쿼리를 작성할 때 QClass를 기반으로 쿼리를 실행한다.

 

그렇다면 QueryDSL을 사용하는 이유부터 알아야 한다.

최근에는 Spring Data JPA를 통해 사용자가 SQL을 직접 작성하지 않고도

데이터베이스에 저장된 데이터를 조작할 수 있는 점에서 개발자들이 편리하게 사용하였다.

하지만 JPA를 통해 기존의 SQL을 통해 수행했던 기능들이 모두 가능한 것은 아니다.

Spring Data JPA가 자동으로 구현체를 만들어주는 메서드와

Entity만 사용할 경우 다음과 같은 문제점이 존재한다.

 

1. 직접적인 연관관계를 맺지 않는 Entity의 Join이 어렵다.

만약 Entity A, B, C 사이의 연관 관계가 위 도식과 같이

A -(One to Many) → B -(One to Many) → C로 설정되었을 때, 

ARepository를 사용하여 특정 조건을 만족하는 C를 가진 A를 검색할 수 없다. 

Repository를 통해 검색하지 않고,

A 내부의 B 리스트를 순회하며 다시 B 내부의 C 리스트를 순회하며 찾게 되면,

One to Many 연관관계의 기본 FetchTypeLazy이기 때문에, 수많은 단계의 반복문, select 쿼리,

비교연산이 발생할 수 있으며, 최악의 경우 연관된 모든 Entity가 메모리에 로드될 수 있다.

 

One to Many 연관관계의 FetchTypeEager로 설정하면,

A와 연관된 모든 B와 C를 읽으므로 Join 쿼리를 통해 데이터를 한 번에 읽을 수 있지만,

A와 연관된 모든 Entity를 읽어오게 되며,

여전히 반복문과 비교연산을 통해 원하는 Entity를 탐색해야 하는 문제가 있다.

One to Many 연관관계를 양방향으로 설정하고 FetchTypeEager이면,

C를 찾을 때, B와 A도 함께 데이터베이스에서 읽기 때문에 여러 단계의 Join 쿼리를 실행시킬 수 있다.

다만 FetchType을 Eager로 설정한 경우,

연관된 Entity가 필요하지 않은 경우에도 해당 Entity를 읽는 문제가 존재한다.

 

2. Entity들 중 원하는 필드만 가져올 수 없다.

만약 리소스 A에 대한 정보를 DTO를 통해 반환할 때

연관된 리소스 B의 정보들 중 일부도 함께 반환하고 싶은 경우

ARepository와 BRepository를 통해 Entity A와 Entity B를 각각 반환받은 후,

AResonseDto로 생성해야 한다. 이때, Entity A와 B의 모든 필드들의 데이터를 가져오게 된다.

기존의 SQL을 직접 작성하는 경우 가져오려 하는 데이터를

직접 select문에 명시하여 필요한 데이터만을 가져올 수 있었지만,

JPAReposirtory를 사용하는 경우에는 Entity의 멤버의 종류가 많거나 Join 대상이 많아지는 경우,

필요한 데이터에 비해 너무 많은 데이터를 읽어오게 될 수 있다.

위와 같은 문제로 복잡한 Join이 필요하거나, 한 레코드의 크기가 커서 모두 가져오기 부담스러운 경우,

문제를 최적화하기 위하여 JPQL이나 QueryDSL을 사용할 수 있다.

그렇다면 JPQL을 사용하는 예시를 살펴보자.

 

JPQL

먼저 JPQL이란 JPA에서 지원하는 다양한 쿼리 방법 중 가장 단순한 조회 방법으로,

SQL의 경우에는 DB 테이블을 대상으로 쿼리를 질의하지만,

JPQL은 엔티티 객체를 대상으로 쿼리를 질의한다. JPQL을 사용하는 자바 코드를 살펴보면 다음과 같다.

@Repository
public class ArmyCertificateEntityManager {

    @PersistenceContext
    private EntityManager entityManager;

    public List<String> findDistinctCategoryByArmyCode(String armyCode) {
        return entityManager.createQuery(
                        "SELECT DISTINCT ac.category FROM ArmyCertificate ac WHERE ac.armyCode = :armyCode", String.class)
                .setParameter("armyCode", armyCode)
                .getResultList();
    }
}

 

위의 자바 코드를 살펴보면 여전히 쿼리문을 문자열 자체로 작성해야 하고,

구문 작성 시 type-check가 불가능하기 때문에 오타 및 관리에 있어서 어려움이 따른다.

또한 컴파일 단계에서 오류를 확인할 수 없고, 런타임에서 해당 쿼리가 실제 실행되어야

오류를 발견할 수 있다. 테스트 코드를 통해 사전에 오류를 예방가능하지만,

프로그램을 운영하면서 오류가 발생할 수도 있다는 부담을 가지고 있는 것은 여전하다.

이에 대한 단점을 보완하기 위해 QueryDSL JPA를 사용하는 것이다.

 

 

QueryDSL JPA의 사용 이유

먼저 QueryDSL JPA의 장점들을 살펴보면 다음과 같다.

  • 타입 안전한 쿼리
    JPQL이나 SQL을 문자열로 작성하면 런타임에 오류를 발견하게 된다.
    하지만 QueryDSL은 Java 코드로 쿼리를 작성하므로
    컴파일 타임에 오류를 발견할 수 있어 타입 안정성을 보장한다.
  • 가독성
    메서드 체이닝을 사용해 직관적이고 가독성이 높은 코드를 제공한다.
  • 동적 쿼리 작성
    동적으로 조건을 추가하거나 변경할 수 있는 유연한 쿼리를 작성할 수 있다.

코드 예시를 통해 살펴보면 다음과 같다.

회원(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 JPA를 사용했을 때에는 아래와 같이 코드를 사용해서 나타낼 수 있다.

return jpaQueryFactory
.from(member)
.join(member.point, point)
.fetch();

 

즉 오타가 나더라도 컴파일 단계에서 오류를 확인할 수 있고,

쿼리문 작성이 아닌 자바 코드로 작성하기 때문에 더욱 객체 지향적으로 개발할 수 있다.

이러한 이유로 Spring에서 JPA를 사용할 때 QueryDSL JPA를 보통 함께 사용한다.

Spring Data JPA는 복잡한 Join 혹은 동적 쿼리를 구현하는 데 있어서 한계가 존재하기 때문에,

복잡한 Join 연산 혹은 동적 쿼리를 프로젝트 내에서 구현해야 한다면,

QueryDSL JPA를 같이 사용하는 것이 효율적이다.

또한 쿼리 작성 시 제약 조건 등을 메서드 추출을 통해 재사용할 수 있고,

JPQL 문법과 유사한 형태로 작성할 수 있어 쉽게 적응할 수 있을 것이다.

 

 

참고자료

https://blog.naver.com/innogrid/222725730056

https://tecoble.techcourse.co.kr/post/2021-08-08-basic-querydsl/

반응형

loading