[Spring] QueryDSL에서 공통 정렬 조건 처리하기!

sinryuji·2025년 2월 26일
post-thumbnail

문제 상황

QueryDSL을 사용해서 정렬을 수행하려면 orderBy()OrderSpecifier를 전달해주어야 한다. 다음과 같이 말이다.

OrderSpecifier<?> orderSpecifier;
switch (sortBy) {
    case CREATED_AT -> {
        if (sortDirection == Direction.ASC) {
            orderSpecifier = deliveryAddress.createdAt.asc();
        } else {
            orderSpecifier = deliveryAddress.createdAt.desc();
        }
    }
    case UPDATED_AT -> {
        if (sortDirection == Direction.ASC) {
            orderSpecifier = deliveryAddress.updatedAt.asc();
        } else {
            orderSpecifier = deliveryAddress.updatedAt.desc();
        }
    }
    default -> orderSpecifier = deliveryAddress.createdAt.desc();
}

List<DeliveryAddress> resultList = queryFactory
    .selectFrom(deliveryAddress)
    .where(builder)
    .offset(pageable.getOffset())
    .orderBy(orderSpecifier)
    .limit(pageable.getPageSize())
    .fetch();

이 때 createdAt이나 updatedAt과 같은 공통 필드를 가지고 정렬을 수행해야 할 때가 있다. 그런데 OrderSpecifierdeliveryAddress.createdAt.asc()와 같이 QClass에서 가져오기 때문에 엔티티마다 저 switch 코드를 작성해야 하는 불편함이 있었다.

그래서 이를 해결하기 위해 공통 필드들, 즉 BaseEntity에 있는 필드들에 대한 OrderSpecifier를 제공해주는 함수를 구현하였다.

공통 필드 정렬

public static OrderSpecifier<?> getCommonOrderSpecifier(
    EntityPathBase<? extends BaseEntity> qClass,
    Sort.Direction sortDirection,
    CommonSortBy sortBy) {
    PathBuilder<BaseEntity> entityPath = new PathBuilder<>(BaseEntity.class,
        qClass.getMetadata());

    switch (sortBy) {
        case CREATED_AT -> {
            return sortDirection == Sort.Direction.ASC
                ? entityPath.getDateTime("createdAt", LocalDateTime.class).asc()
                : entityPath.getDateTime("createdAt", LocalDateTime.class).desc();
        }
        case UPDATED_AT -> {
            return sortDirection == Sort.Direction.ASC
                ? entityPath.getDateTime("updatedAt", LocalDateTime.class).asc()
                : entityPath.getDateTime("updatedAt", LocalDateTime.class).desc();
        }
        default -> throw new IllegalArgumentException("지원되지 않는 정렬 조건: " + sortBy);
    }
}

내가 최종적으로 작성한 코드는 위와 같다 하나씩 살펴보자.

QClass들을 보면 모두 다음과 같이 EntityPathBase<T>를 상속하고 있다.

@Generated("com.querydsl.codegen.DefaultEntitySerializer")
public class QDeliveryAddress extends EntityPathBase<DeliveryAddress> {

그래서 EntityPathBase<T>를 이용하여 QClass들에 대한 공통적인 처리가 가능하다. EntityPathBase<? extends BaseEntity> qClass로 타입을 받으면 BaseEntity를 상속한 엔티티들에 대한 QClass 타입이 되는 것이다.

그리고 PathBuilder<T> 역시 EntityPathBase<T>를 상속하고 있으며 QueryDSL에서 문자열로 속성에 접근할 수 있도록 해주는 클래스이다.

public class PathBuilder<T> extends EntityPathBase<T> {

	...

    public PathBuilder(Class<? extends T> type, PathMetadata pathMetadata) {
        this(type, pathMetadata, PathBuilderValidator.DEFAULT);
    }
}

EntityPathBase<T>getMetadata()PathMetadata를 반환하는 함수로 PathMetadata는 엔티티의 경로를 표현하는데 필요한 메타 데이터를 다루는 클래스이다.

엔티티 클래스와 PathMetadata 통해 PathBuilder<T>를 초기화 할 수 있다. 우리는 BaseEntity의 필드들에 대한 접근이 필요하므로 BaseEntity.class와 파라미터로 받은 QClass의 PathMetadata를 전달해주면 된다.

 entityPath.getDateTime("createdAt", LocalDateTime.class).asc()

그렇게 초기화 한 객체에서 위와 같이 필드에 접근 할 수 있다. createdAtLocalDateTime 타입이므로getDateTime()을 사용해주었고, 필드명 문자열과 필드의 타입을 파라미터로 전달해주면 된다. 그 후 asc()와 같이 QClass의 필드에 접근이 가능하다!

OrderSpecifier<?> orderSpecifier = PageUtils.getCommonOrderSpecifier(deliveryAddress,
    sortDirection, sortBy);
OrderSpecifier<?> orderSpecifier = PageUtils.getCommonOrderSpecifier(order, sortDirection,
    sortBy);

그럼 이제 QClass마다 복잡한 switch 구문을 작성할 필요 없이 위와 같이 QClass만 파라미터로 전달해주면 간단히 OrderSpecifier를 초기화 할 수 있다!!

profile
응애 개발자입니다.

0개의 댓글