springboot

[SpringBoot] QueryDSL 그게 뭔데 다들 쓰는거지?

inhooo00 2024. 12. 28. 18:47

📍Query DSL ?

DSL:특정 도메인에서 발생하는 문제를 효과적으로 해결하기 위해 설계된 언어.
Query DSL : DSL 언어를 제공해주는 라이브러리.

즉,  쿼리를 타입 안전하게 자바 문법으로 작성, 메서드 체이닝으로 가독성 상향, 단순하고 간결함을 위해서 사용한다.

 

 

 

📍타입이 왜 안전할까?

  • @Entity가 붙은 클래스에 맞춰서 QClass가 컴파일 타임에 자동으로 생성된다.
@Entity
public class User {
    @Id
    private Long id;
    private String name;
    private Integer age;
}
public class QUser extends EntityPathBase<User> {
    public final StringPath name = createString("name");
    public final NumberPath<Integer> age = createNumber("age", Integer.class);
    public final NumberPath<Long> id = createNumber("id", Long.class);
}

각 필드가 정확한 타입(StringPath, NumberPath)으로 정의되어 있어 잘못된 타입의 연산을 작성하면 컴파일 에러가 발생하게 된다.

  • NumberExpression과 StringExpression을 통해서 다양한 메서드가 정의되어 있으며, 우리가 만약 타입이 다른 연산을 실행하면 컴파일 타임에 오류가 발생하며 타입 안정성이 유지된다.
BooleanExpression isAdult = user.age.gt(18);
BooleanExpression startsWithJ = user.name.startsWith("J");

이렇게 메서드가 QClass 안에 구현되어 있어서 기존 자바와 같이 문법 오류를 잡아낼 수 있다.

 

 

 

📍중복 조건식 재사용

  • JPQL 쿼리는 문자열이기 때문에 조건을 캡슐화하거나 재사용하기 어렵다.
  • QueryDSL은 조건 표현(Expression 영역)JPQL 생성/실행 영역을 분리하므로, 조건을 메서드로 캡슐화하여 재사용할 수 있다.
// 조건식 캡슐화 (Expression 영역)
private BooleanExpression isActive() {
    return QUser.user.status.eq("ACTIVE");
}

private BooleanExpression isOlderThan(int age) {
    return QUser.user.age.gt(age);
}
// JPQL 생성 및 실행 (JPAQueryFactory)
List<User> users = queryFactory.selectFrom(QUser.user)
    .where(isActive().and(isOlderThan(18))) // 조건 재사용
    .fetch();

// 다른 쿼리에서도 재사용 가능
long userCount = queryFactory.selectFrom(QUser.user)
    .where(isActive().and(isOlderThan(30))) // 동일 조건 사용
    .fetchCount();

위 예시는 짧은 비교 쿼리문이지만, 복잡한 쿼리를 메서드화하게 된다면 더욱 효율적으로 코드를 구현할 수 있게 된다.

 

 

 

📍깔끔한 동적 쿼리 작성

public List<User> searchUsers(String name, Integer age, String status) {
    BooleanBuilder builder = new BooleanBuilder();

    if (name != null && !name.isEmpty()) {
        builder.and(QUser.user.name.contains(name));
    }

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

    if (status != null && !status.isEmpty()) {
        builder.and(QUser.user.status.eq(status));
    }

    return queryFactory.selectFrom(QUser.user)
                       .where(builder)
                       .fetch();
}

복잡한 조건일수록 문자열 조합이 혼란스러울 수 있는 SQL에 반해, DSL은 상대적으로 깔끔하게 동적쿼리 관리가 가능하다.