본문 바로가기
Spring/JPA

[QueryDSL] QueryDSL이란?

by 진꿈청 2024. 3. 30.

QueryDSL이란?

 

우선, DSL은 Domain Specific Language의 줄임말이다.

뜻은 특정 영역에 특화된 언어이다.

그렇기 때문에 QueryDSL은 쿼리 생성에 특화된 프레임워크이다.

 

우리가 QueryDSL를 사용하면 좋은 점이 무엇이 있을까?

 

그것에 관한 설명을 하기 전에 JPA와 JPQL에 관한 문제점에 대해 잠깐 알아보자.

 

 

JPA

JPA는 객체지향 어플리케이션과 RDBMS 사이의 패러다임 불일치를 해결해주는 역할을 하는 프레임워크이다.

JPA 덕분에 우리는 객체지향 관점으로 개발할 수 있게 되고 JPA 프레임워크는 자동으로 SQL 쿼리문을 생성해준다.

 

SQL문이 JPA에 의해 자동으로 생성되기에 개발자는 SQL 관점에서 프로그래밍을 하지 않아도 된다.

하지만, 그렇다하더라도 완전한 분리는 불가능하다.

따라서, 복잡한 쿼리를 생성하기 위해 JPA는 JPQL을 지원한다.

 

JPQL

JPQL은 SQL문과 상당히 유사하다. 객체지향적 관점을 유지하는 SQL 문이라도 생각하면 된다.

 

그러나, JPQL에는 큰 문제가 두 가지가 있다.

 

타입 안정성이 떨어짐

String jpql = "SELECT p.category, AVG(p.price) FROM Product p " +
              "GROUP BY p.category " +
              "HAVING AVG(p.price) > :averagePrice";

 

JPQL은 문자열이므로 개발자는 JPQL 문법이 틀려도 알아차리기가 어렵다.

그렇기에 런타임 중에 메소드가 호출이 되고 해당 JPQL이 파싱될 때 문법오류 발견이 가능하다.

 

@Query 어노테이션을 사용하면 프로그램이 올라가는 과정에 문법 오류를 발견할 수 있다. 하지만, 이것도 결국은 실행해야 한다.

즉, JPQL을 파싱하는 모종의 프로세스 동작시에만 문법오류 발견이 가능하며 컴파일 과정에서는 오류 발견이 불가능하다.

 

이런 이유들로 타입 안정성이 떨어진다.

 

직관적인 동적쿼리 작성이 어려움

 

JPQL은 문자열이므로 동적쿼리를 작성하려면 문자열을 조작해야 한다.

하지만, 이는 문자열과 문자열 사이에 if문, for문이 들어가게 되고 이게 길어진다면 가독성이 떨어진다.

 

String jpqlQuery = "select m from Member m";

if(id != null){ //id가 NULL이 아니면
    jpql += "where id = :id"; // WHERE 조건문 추가
}

TypedQuery<Member> query = em.createQuery(jpqlQuery, Member.class);

if(id != null){ //id가 NULL이 아니면
    query = query.setParameter("id", id); // 파라미터 추가
}

 

물론, JPA가 지원하는 동적쿼리 기술이 존재한다. 대표적으로 JPA 표준기술인 Criteria가 있지만 직관적이지 못하다. 

 

하지만, 우리가 알아볼 QueryDSL은 JPA가 지원하는 표준기술은 아니지만 타입 안정성이 보장되며,

직관적인 동적쿼리 작성이 가능하다는 장점이 있다.

 

이런 이유로 실제로 JPA와 연계되어 가장 많이 사용되는 프레임워크 중 하나이다.

 

그렇다면 그런 QueryDSL은 어떻게 동작하는 걸까?

 

 

QueryDSL 동작원리

기존 JPQL 수행원리

앞서 말했던 것처럼 개발자(클라이언트)가 JPQL을 문자열로 직접 작성하면,

타입 안정성 체크가 어려우며 직관적인 동적쿼리도 작성할 수 없다.

반면에 QueryDSL은 JPQL을 대신 생성해주기에 이러한 문제의 해결이 가능하다.

 

QueryDSL에서 JPQL이 실행되는 원리

 

QueryDSL의 목적은 개발자 대신 JPQL을 생성해주는 것이다.

 

개발자는 QueryDSL이 JPQL을 생성할 수 있도록 필요한 데이터를 세팅 및 전달한다.

이때, Entity의 정보가 필요한데 Entity는 JPA 프레임워크에서 지원하는 모듈이다.

 

하지만, QueryDSL은 쿼리생성에 특화된 프레임워크로 JPA 프레임워크와는 분리되어 있다.

그러므로 JPA 프레임워크의 모듈을 그대로 사용하면 JPA 프레임워크에 종속되어버리는 꼴이 된다.

 

해당 현상을 막기 위해 QueryDSL은 Entity 정보를 담은 Q타입클래스를 사용한다.

 

Q타입클래스는 QueryDSL 플러그인으로 컴파일하면 지정된 위치에 생성된다.

개발자는 Q타입객체를 생성하여 JPQL 생성에 필요한 데이터를 QueryDSL 프로세스에 넘길 수 있다.

 

QueryDSL 플러그인 설정

//querydsl dependencies 추가(스프링부트 3.0 이상)
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

 

해당 의존성을 추가하고 gradle build를 하면 된다.

 

QueryDSL 작성 예

public List<Member> searchMember(MemberSearchCondition condition){
    QMember qMember = QMember.member; //Q타입 클래스 객체 생성

    return queryFactory
            .select(qMember) // select 메소드
            .from(qMember) // from 메소드
            .leftJoin(member.team, team) // leftJoin메소드
            .where( // where 메소드
                    usernameEq(condition.getUsername()), 
                    teamNameEq(condition.getTeamName()),
                    ageGoe(condition.getAgeGoe()),
                    ageLoe(condition.getAgeLoe())
            )
            .fetch(); //fetch메소드
}

 

QueryDSL 코드의 목적은 JPQL 생성용 데이터 세팅이다.(JPQL은 QueryDSL 프로세스가 생성해주기 때문)

그러므로 QueryDSL은 데이터 세팅에 특화된 디자인 패턴인 빌더 패턴 구조이다.

 

SELECT 절에 필요한 데이터는 select 메소드에 세팅하며 FROM 절에 필요한 데이터는 from 절에 세팅한다.

또한, WHERE 절에 필요한 데이터는 where 메소드에 세팅하면 된다.

 

해당 메소드들은 파이프라인 구조로 되어있기에 가독성은 더욱 올라간다.

 

QueryDSL 사용시 유의할 점은 가장 중요한 데이터인 엔티티 데이터는 Q타입클래스 객체로 전달해야 한다.

그렇다고 꼭 엔티티만 Q타입객체로 만들어 전달할 수 있는 것은 아니다.

일반객체도 Q타입객체로 만들어 전달할 수 있다.

 

일반객체 사용 예시

JPAQueryFactory queryFactory = new JPAQueryFactory(em);
QUser qUser = QUser.user;

List<UserDto> userDtos = queryFactory.select(Projections.constructor(UserDto.class,
                                                                      qUser.name,
                                                                      qUser.email))
                                     .from(qUser)
                                     .where(qUser.email.like("%@gmail.com"))
                                     .fetch();


이런식으로 직접 DTO로 뽑는 것도 가능하다.

 

정리

QueryDSL이 JPQL의 생성을 대신하기에 개발자는 타입 안정성 체크가 가능해지며
빌더 패턴 구조를 통해 가독성에서 이점을 얻는다.

 

 

참고

https://www.inflearn.com/course/querydsl-%EC%8B%A4%EC%A0%84

 

실전! Querydsl | 김영한 - 인프런

김영한 | Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, 복잡한 쿼리, 동적 쿼리는 이제 안녕! Querydsl로 자바 백엔드 기술을 단단하게. 🚩 본 강의는 로드맵 과정입니다. 본 강의는 자

www.inflearn.com