동적 쿼리란? - Querydsl

 Querydsl은 Java 기반의 타입 안전한 동적 쿼리 생성 도구로, SQL, JPA, MongoDB 등 다양한 데이터베이스와 연동할 수 있다. 특히 JPA와 함께 사용하면 JPA의 동적 쿼리 작성이 더욱 간단하고 효율적으로 바뀐다. JPQL(Java Persistence Query Language)의 단점을 극복하기 위해 등장한 Querydsl은 컴파일 타임에 오류를 감지할 수 있는 타입 안전성을 제공하며, 동적 쿼리를 작성하는 데 뛰어난 가독성과 간결함을 제공한다.

 

build.gradle 설정

dependencies {
    implementation 'com.querydsl:querydsl-jpa:5.0.0'
    annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jpa'
}

 

컴파일 설정

tasks.withType(JavaCompile) {
    options.annotationProcessorGeneratedSourcesDirectory = file("$buildDir/generated/querydsl")
}

 

 

Querydsl 사용 방법

  Querydsl은 JPQL의 문자열 기반 접근 방식에서 발생하는 문제를 해결하기 위해 등장했다. 타입 안전성, 동적 쿼리 작성의 간결함, 가독성 높은 코드 등 다양한 장점을 제공하며, JPA와 함께 사용하면 복잡한 동적 쿼리를 간단하게 처리할 수 있다. 특히 조건문이 많은 복잡한 쿼리나 유지보수성이 중요한 프로젝트에서 강력한 도구로 활용될 수 있다.

동적 쿼리란?

 동적 쿼리(Dynamic Query)란, 실행 시점에 조건이나 값에 따라 쿼리의 구조를 동적으로 변경할 수 있는 쿼리를 말한다.
즉, 정적인 쿼리가 아닌, 상황에 따라 쿼리 조건을 추가하거나 제거해 실행되는 SQL이다.

 예를 들어, 사용자로부터 입력받은 검색 조건에 따라 WHERE 절이나 ORDER BY 절이 달라지는 쿼리를 작성해야 할 때, 이를 동적 쿼리로 처리할 수 있다.

 

실제 애플리케이션에서는 다음과 같은 요구사항이 자주 발생한다.

 

사용자 검색 조건이 고정되지 않은 경우

 - 사용자가 이름으로 검색할지, 나이로 검색할지, 혹은 둘 다 검색할지 알 수 없다.

 - 조건이 여러 개인 경우, 특정 조건이 입력되지 않으면 쿼리에서 해당 조건을 제외해야 한다.

 

필터링 및 정렬 기능이 필요한 경우

 - 쇼핑몰에서 가격, 카테고리, 브랜드, 인기순 등을 선택적으로 필터링 및 정렬.

 

복잡한 쿼리에서 조건을 조합하거나 제거해야 할 경우

 - 예를 들어, 관리자가 특정 날짜 범위와 상태에 따라 데이터를 조회하려고 할 때, 이 조건들을 조합할 수 있어야 한다.

 

정적 쿼리와 동적 쿼리 비교

정적 쿼리

조건이 고정되어 있다.

쿼리의 형태가 변하지 않고 항상 동일한 쿼리가 실행된다.

 

예시: 특정 이름과 나이로 사용자를 조회하는 정적 쿼리.

SELECT * FROM users WHERE name = 'John' AND age >= 20;

 

동적 쿼리

실행 시점에 조건이 달라질 수 있다.

특정 조건이 입력되지 않으면 해당 조건을 쿼리에서 제외한다.

 

예시: 이름이나 나이, 또는 둘 다 입력받아 검색하는 동적 쿼리.

SELECT * FROM users
WHERE (:name IS NULL OR name = :name)
  AND (:age IS NULL OR age >= :age);

 

동적 쿼리 작성 방법

1. JDBC로 동적 쿼리 작성 (기본 방식)

JDBC에서는 쿼리를 직접 문자열로 생성해야 한다. 동적 쿼리는 문자열의 조건을 추가/제거하는 방식으로 작성된다.

String sql = "SELECT * FROM users WHERE 1=1";

if (name != null) {
    sql += " AND name = ?";
}
if (age != null) {
    sql += " AND age >= ?";
}

try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
    int index = 1;
    if (name != null) {
        pstmt.setString(index++, name);
    }
    if (age != null) {
        pstmt.setInt(index++, age);
    }
    ResultSet rs = pstmt.executeQuery();
    while (rs.next()) {
        System.out.println("Name: " + rs.getString("name"));
    }
}

 

 

 - 조건이 많아질수록 코드가 복잡해지고, 가독성이 떨어진다.

 - 쿼리 문자열을 수동으로 조합해야 하기 때문에 유지보수가 어렵다.

 

2. JPA로 동적 쿼리 작성

 JPA에서는 JPQL(Java Persistence Query Language)을 사용해 동적 쿼리를 작성할 수 있다.
하지만 JPQL은 문자열 기반으로 작성되므로, 조건이 많아질수록 가독성과 유지보수성이 떨어진다. 이를 해결하기 위해 Criteria API를 사용할 수 있다.

String jpql = "SELECT u FROM User u WHERE 1=1";

if (name != null) {
    jpql += " AND u.name = :name";
}
if (age != null) {
    jpql += " AND u.age >= :age";
}

TypedQuery<User> query = entityManager.createQuery(jpql, User.class);

if (name != null) {
    query.setParameter("name", name);
}
if (age != null) {
    query.setParameter("age", age);
}

List<User> result = query.getResultList();

 

 

3. Querydsl로 동적 쿼리 작성

 Querydsl은 동적 쿼리를 작성하기 위한 강력한 도구이다. 조건을 쉽게 추가하거나 제거할 수 있으며, 컴파일 시점에 타입을 확인할 수 있어 안정성과 가독성을 모두 만족한다.

import com.querydsl.jpa.impl.JPAQueryFactory;
import com.querydsl.core.types.dsl.BooleanExpression;

@Service
public class UserService {
    @PersistenceContext
    private EntityManager entityManager;

    public List<User> findUsers(String name, Integer age) {
        JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
        QUser user = QUser.user;

        BooleanExpression nameCondition = name != null ? user.name.eq(name) : null;
        BooleanExpression ageCondition = age != null ? user.age.goe(age) : null;

        return queryFactory.selectFrom(user)
                           .where(nameCondition, ageCondition)
                           .fetch();
    }
}

 

 

 - BooleanExpression을 사용해 조건을 동적으로 생성한다.

 - where() 메서드에 조건을 전달하며, null인 조건은 무시된다.

 - 조건을 쉽게 추가하거나 제거할 수 있다.