QueryDSL with Kotlin - 기본 문법과 groupBy, 집계함수 사용

DevSeoRex·2023년 4월 30일
2
post-thumbnail

QueryDSL을 시작하다!

순수 JPA를 거쳐서 Spring Data JPA까지 순조롭게 학습을 마쳐가고 있는 와중에 대부분 JPA를 사용하는 분들이라면 떼놓을 수 없는 관계인 QueryDSL 학습을 시작하게 되었습니다.

Spring Data JPA를 사용할때도 꽤나 편리한 기능이 많았고 그런 기능들만 누려도 이미 행복지수가 하늘을 찌르고 있지만.. QueryDSL의 매력에 푹 빠져서 하나하나 뜯어 보겠습니다.

오픈 소스로 시작한 작은 혁명

QueryDSL은 놀랍게도 JPA에서 공식 지원하는 기능이 아닌 오픈소스 프로젝트 입니다.
김영한 강사님도 강의중에 언어의 한계를 뛰어넘으려고 시도한 케이스라며 굉장히 찬사를 보냈던 것으로 기억합니다. QueryDSL 지금부터 어떤 녀석인지 하나 하나 뜯어보겠습니다.

QueryDSL 설정

저는 코프링을 사용중이기 때문에 build.gradle.kts 파일에 아래와 같이 작성해주겠습니다.

plugins {
	...
	kotlin("plugin.noarg") version "1.6.21"
	kotlin("kapt") version "1.7.10"
}

dependencies {
	...
	implementation("com.querydsl:querydsl-jpa:5.0.0")
	kapt("com.querydsl:querydsl-apt:5.0.0:jpa")
}

QueryDSL은 JPAQueryFactory라는 클래스를 이용해서 쿼리를 만들어줍니다.
JPA 표준 스펙인 Criteria와 같이 JPQL Builder 역할을 수행해줍니다.

QueryDSL의 약간 특이한 점은 모든 엔티티를 Q를 앞에 붙인 Q타입 클래스를 컴파일 시점에 만들어 준다는 것입니다. 이 Q타입을 이용해 QueryDSL은 쿼리를 만들어냅니다.

JPAQueryFactory를 사용하려면, EntityManager를 객체를 생성할 때 파라미터에 넣어주어야 하는데,
사용하는 곳 마다 매번 이 코드를 작성하는 것은 비효율적이기 때문에 설정 클래스를 작성해주겠습니다.

Entity 클래스 작성

이번 QueryDSL 포스팅 예제에서 사용할 회원(Member)과 팀(Team) 엔티티를 작성하겠습니다.


회원과 팀은 1 : N 관계를 가지고 있고, 기본적인 fetch 전략은 LAZY로 설정하였습니다.

QueryDSL 무엇이 다를까?

기존에 사용하던 EntityManager의 createQuery나 Spring Data JPA의 여러 기능들과 QueryDSL은 어떤 점이 다르고 어떤 부분에서 개발자에게 편의기능을 제공할까요?

일단 직접 JPQL을 작성해서 전체 회원을 조회하는 코드를 작성하겠습니다.

테스트를 실행하게 되면,

위와 같은 쿼리가 나가게 됩니다.
그렇다면 똑같은 기능을 하는 코드를 QueryDSL을 이용해서 작성해보겠습니다.

이 테스트 코드를 실행해도,

위와 같이 같은 쿼리가 나가는 것을 볼 수 있습니다. 결국 두 코드는 같은 기능을 하는 것이 증명되었습니다.

조회 - 전체 조회 & 한 건 조회 & 카운팅 쿼리 변경

QueryDSL에서는 fetch, fetchOne, fetchFirst, fetchCount 등의 함수를 이용할 수 있습니다.

  • fetch : 내부적으로 getResultList 함수를 사용합니다. 즉 엔티티를 List에 감싸서 return 합니다.
  • fetchOne: 내부적으로 getSingleResult 함수를 사용합니다. 하나의 엔티티만 반환하며, 조회결과가 한개가 아니라면 NonUniqueException이 발생하며, 조회 결과가 없을경우 NoResultException이 발생합니다.
  • fetchFirst: limit 1을 옵션으로 준것과 똑같이 동작합니다.
  • fetchCount: 데이터 리스트를 조회하지 않고 카운팅 쿼리로 변경해서 동작합니다. 현재는 Deprecated 되었습니다.

GroupBy 함수 사용과 FetchJoin

이제 예시 케이스를 하나 들어보겠습니다.

팀별의 이름과 팀별 평균나이를 구하는 쿼리를 하고 싶을때 어떻게 QueryDSL로 코드를 작성할 수 있는지 지금부터 코드를 작성해보겠습니다.

지금까지 작성했던 코드와 다른 부분은 join을 하는 부분과 groupBy를 하는 부분입니다.
쿼리가 어떻게 나가는지 확인해보겠습니다.

쿼리가 예상한대로 잘 나간것을 볼 수 있습니다.
여기서 QueryDSL의 가장 큰 장점이 나옵니다. 지금 나간 쿼리와 작성한 코드를 보면, 자바 코드를 이용해서 마치 쿼리를 작성하는 것처럼 코드가 작성되어 있습니다.

이렇게 쿼리를 자바 코드를 이용해서 만들어도 굉장히 직관적으로 볼 수 있다는 것이 큰 장점인것 같습니다.

FetchJoin
FetchJoin은 어떻게 QueryDSL을 이용해서 작성할 수 있을까요?

기존에 createQuery를 이용한 방법이라면 아래와 같이 작성할 수 있습니다.

이 코드를 QueryDSL을 이용해서 작성해 보곘습니다.

정말 코드가 간결해진것을 볼 수 있습니다. join을 한 이후에 fetchJoin만 호출해주면 간단히 문제를 해결할 수 있습니다.

쿼리도 예상을 빗나가지 않고 제대로 나가주는 것을 볼 수 있습니다.

집계 함수 사용

집계함수는 count, max, min, avg와 같이 말그대로 집계를 위해 사용하는 함수를 이야기합니다.
QueryDSL을 이용하면 어떻게 집계함수를 사용할 수 있는지 확인해보겠습니다.

간단하게 현재 회원의 수(count)와 회원 중 가장 많은 나이(max)를 구해보겠습니다.

예상대로 쿼리가 잘 나가는 것을 볼 수 있습니다.

다음으로..!!

QueryDSL의 가장 기본적인 부분을 실습하고 포스팅 해봤습니다. 지금 나열했던 이 기능들 이외에도 훨씬 강력한 편의성을 제공하는 기능들이 매우 많습니다.
예를 들면 BooleanExpression을 이용한 조건 조립(where 다중 파라미터 사용), QueryProjections 등이 있지만 그 부분은 하나 하나 정리해서 또 들고 오겠습니다.

출처 : 인프런 (실전! QueryDSL : 김영한)

0개의 댓글