QueryDSL

haruceki·2024년 9월 15일
0

Query Domain Specific Language(도메인 특화 언어)의 약자인 QueryDSL는 데이터베이스 쿼리를 객체 지향적으로 작성할 수 있게 해준다. Java를 포함한 여러 언어에서 사용되는 세이프 타입 SQL 쿼리 빌더로, JPA와 같은 ORM(Object-Relational Mapping) 프레임워크에서 SQL 쿼리를 자바 코드로 작성할 수 있도록 도와준다. Querydsl을 사용하면 SQL과 유사한 구문을 사용해 동적 쿼리를 쉽게 작성할 수 있으며, 컴파일 타임에 쿼리의 정확성을 보장해주는 장점이 있다.

JPQL을 이용할 경우 복잡한 조건을 작성할 때 가독성이 떨어지며, 문자열 기반 쿼리므로 컴파일 타임에 문법 오류를 검출하지 못하는 등 복잡한 로직을 처리하기 어려울 때 QueryDSL을 사용하는 경우가 많다.

주요 특징

  1. 타입 안정성 : 쿼리를 작성할 때 컴파일 타임에 필드 이름과 데이터 타입을 검증할 수 있다. 이는 런타임 에러를 줄이고 잘못된 쿼리를 작성하지 않도록 도와준다.
  2. 직관적인 문법 : SQL과 유사한 문법을 사용하므로 기존 SQL 사용자들이 쉽게 적응 가능하다. 또한 메서드 체이닝을 통해 간결하고 직관적인 쿼리 작성이 가능하다.
  3. 동적 쿼리 작성 : 복잡한 동적 쿼리를 쉽게 생성가능하도록 다양한 기능을 제공한다. BooleanBuilder나 Expressions 같은 도구들을 통해 조건에 따라 쿼리를 동적으로 조합할 수 있다.
  4. 범용성 : JPA, MongoDB, SQL,Collections 등 다양한 데이터 소스와 함께 사용할 수 있다. 하지만 JPA와 함께 사용할 때 가장 많이 쓰인다.

장단점

장점
  • SQL의 직관성과 Java 코드의 타입 안전성을 모두 제공.
  • 동적 쿼리 작성이 간단하고 강력함.
  • 엔티티 필드명을 직접 참조하기 때문에 IDE 자동 완성 및 컴파일 타임 에러 방지 가능.
  • 복잡한 조건의 쿼리도 코드의 가독성을 유지하며 작성할 수 있음.
단점
  • 초기 설정이 조금 복잡할 수 있음.
  • Q 클래스 생성 및 관리가 필요함 (빌드 시 자동으로 생성되지만 코드 베이스에 추가적인 관리 요소가 생김).

설정

  1. 의존성 추가
dependencies {
	implementation "com.querydsl:querydsl-jpa:5.0.0:jakarta"
	annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
	annotationProcessor "jakarta.annotation:jakarta.annotation-api"
	annotationProcessor "jakarta.persistence:jakarta.persistence-api"
    ...
}
  1. Querydsl 플러그인 설정
plugins {
	id 'java'
	id 'org.springframework.boot' version '3.1.1'
	id 'io.spring.dependency-management' version '1.1.0'
	id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
}
  1. JPAQueryFactory 사용을 위한 config 파일 생성
public class ProductApplicationQueryDslConfig {
    @Bean
    JPAQueryFactory jpaQueryFactory(EntityManager em){
        return new JPAQueryFactory(em);
    }
}
  1. Q 클래스 생성 : 프로젝트 빌드시 자동 생성
    QueryDSL은 엔티티 클래스에 대한 메타모델 클래스(Q 클래스)를 자동으로 생성한다. 이 Q 클래스를 사용하여 엔티티 필드에 타입 안전하게 접근 가능하다.
  • queryDSL이 자동으로 생성한 Q클래스 QProduct.product 예시
QProduct product = QProduct.product;
List<Product> products = queryFactory
	.selectFrom(product)
	.where(product.price.gt(1000))
	.fetch();

예시

기본적인 조회

  • JPAQueryFactory를 사용하여 쿼리를 작성하고, fetch()로 쿼리 결과를 가져온다.
  • fetch()는 쿼리의 결과를 실제로 조회하고, 리스트 형태로 반환하는 역할을 한다.
    • fetchOne(): 단일 결과를 반환하며, 결과가 여러 개일 경우 예외를 던진다.
    • fetchFirst(): 첫 번째 결과만 반환
    • fetchCount(): 결과의 총 개수를 반환
JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
QUser user = QUser.user;

List<User> users = queryFactory
    .selectFrom(user)
    .where(user.name.eq("John"))
    .fetch();

동적 쿼리 : BooleanExpression 또는 BooleanBuilder 사용

  • BooleanExpression은 하나의 Boolean 조건을 나타내는 객체로, 주로 메서드 체이닝 방식으로 여러 조건을 결합할 때 사용

  • BooleanBuilder는 초기조건을 null로 시작하고 동적 조건을 점진적으로 추가할 수 있는 가변 객체

    특징BooleanExpressionBooleanBuilder
    객체 특성불변 객체가변 객체
    초기 상태특정 조건으로 초기화해야 함초기에는 빈 상태로 시작 가능
    사용 방식조건을 메서드 체이닝으로 결합조건을 하나씩 점진적으로 추가
    적합한 경우조건이 적고 간결한 경우조건이 많고 동적 쿼리를 유연하게 구성할 때
    AND/OR 지원and(), or() 사용 가능and(), or() 메서드 사용 가능
  • BooleanExpression 사용

QUser user = QUser.user;
String usernameParam = "john";
String emailParam = "john@example.com";

// 기본 조건
BooleanExpression condition = user.isActive.eq(true);

// 조건 추가
if (usernameParam != null) {
    condition = condition.and(user.username.eq(usernameParam)); // AND 조건
}
if (emailParam != null) {
    condition = condition.or(user.email.eq(emailParam)); // OR 조건
}

// 쿼리 실행
List<User> users = queryFactory
	.selectFrom(user)
    .where(condition)
    .fetch();
  • BooleanBuilder 사용
BooleanBuilder builder = new BooleanBuilder();

if (name != null) {
    builder.and(user.name.eq(name));
}
if (age != null) {
    builder.and(user.age.gt(age));
}

List<User> users = queryFactory
    .selectFrom(user)
    .where(builder)
    .fetch();

페이징 처리

  • limit(): 조회할 데이터의 최대 개수를 지정.
  • offset(): 건너뛸 데이터의 개수를 지정. 이를 통해 페이지 번호에 따른 데이터를 가져올 수 있다.
QUser user = QUser.user;

List<User> users = queryFactory
    .selectFrom(user)
    .where(user.age.gt(18))
    .orderBy(user.name.asc())
    .offset(0) // 앞의 0개는 건너뛰고 그 다음부터 조회
    .limit(10) // 최대 10개의 결과만 반환
    .fetch();

정렬

  • 오름차순 (asc()): 기본적으로 작은 값에서 큰 값으로 정렬.
  • 내림차순 (desc()): 큰 값에서 작은 값으로 정렬.
QUser user = QUser.user;

// Querydsl을 사용하여 사용자 목록을 이름 순으로 오름차순 정렬하여 조회
List<User> users = queryFactory.selectFrom(user)
    .orderBy(user.username.asc()) // 이름 기준으로 오름차순 정렬
    .fetch();

그룹핑

QueryDSL에서 groupBy()는 SQL의 GROUP BY 구문과 동일한 역할을 수행하여 데이터를 그룹화할 때 사용된다. 이와 함께 having(), select() 등과 결합하여 특정 조건에 따라 데이터를 그룹으로 나눈 후, 그 그룹에 대해 집계 함수를 사용할 수 있다.

QUser user = QUser.user;

// Querydsl을 사용하여 나이별 사용자 수 그룹핑
List<Tuple> result = queryFactory
    .select(user.age, user.count()) // 나이와 그 나이에 해당하는 사용자 수를 선택
    .from(user)
    .groupBy(user.age) // 나이(age)로 그룹화
    .fetch();

// 결과 출력
for (Tuple tuple : result) {
    Integer age = tuple.get(user.age);
    Long count = tuple.get(user.count());
    System.out.println("나이: " + age + ", 사용자 수: " + count);
}
profile
희망도 절망도 없이 매일 코딩을 한다.

0개의 댓글