Query Domain Specific Language(도메인 특화 언어)의 약자인 QueryDSL는 데이터베이스 쿼리를 객체 지향적으로 작성할 수 있게 해준다. Java를 포함한 여러 언어에서 사용되는 세이프 타입 SQL 쿼리 빌더로, JPA와 같은 ORM(Object-Relational Mapping) 프레임워크에서 SQL 쿼리를 자바 코드로 작성할 수 있도록 도와준다. Querydsl을 사용하면 SQL과 유사한 구문을 사용해 동적 쿼리를 쉽게 작성할 수 있으며, 컴파일 타임에 쿼리의 정확성을 보장해주는 장점이 있다.
JPQL을 이용할 경우 복잡한 조건을 작성할 때 가독성이 떨어지며, 문자열 기반 쿼리므로 컴파일 타임에 문법 오류를 검출하지 못하는 등 복잡한 로직을 처리하기 어려울 때 QueryDSL을 사용하는 경우가 많다.
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"
...
}
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"
}
public class ProductApplicationQueryDslConfig {
@Bean
JPAQueryFactory jpaQueryFactory(EntityManager em){
return new JPAQueryFactory(em);
}
}
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은 하나의 Boolean 조건을 나타내는 객체로, 주로 메서드 체이닝 방식으로 여러 조건을 결합할 때 사용
BooleanBuilder는 초기조건을 null로 시작하고 동적 조건을 점진적으로 추가할 수 있는 가변 객체
| 특징 | BooleanExpression | BooleanBuilder |
|---|---|---|
| 객체 특성 | 불변 객체 | 가변 객체 |
| 초기 상태 | 특정 조건으로 초기화해야 함 | 초기에는 빈 상태로 시작 가능 |
| 사용 방식 | 조건을 메서드 체이닝으로 결합 | 조건을 하나씩 점진적으로 추가 |
| 적합한 경우 | 조건이 적고 간결한 경우 | 조건이 많고 동적 쿼리를 유연하게 구성할 때 |
| 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 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);
}