JPA

[JPA] QueryDSL에서 Sorting(정렬)하는 Util 메서드 만들기

Happy._. 2024. 6. 24. 15:30

QueryDSL에서는 Pageable의 sort를 그대로 사용할 수 없어서 OrderSpecifier를 사용해 구현했다.

orderBy()의 파라미터 타입이 OrderSpecifier이기 때문이다.

 

OrderSpecifier의 생성자는 다음과 같이 Order와 Expression 타입의 객체를 받는다.

@Immutable
public class OrderSpecifier<T extends Comparable> implements Serializable {
    private static final long serialVersionUID = 3427652988262514678L;
    private final Order order;
    private final Expression<T> target;
    private final NullHandling nullHandling;

    public OrderSpecifier(Order order, Expression<T> target, NullHandling nullhandling) {
        this.order = order;
        this.target = target;
        this.nullHandling = nullhandling;
    }

    public OrderSpecifier(Order order, Expression<T> target) {
        this(order, target, OrderSpecifier.NullHandling.Default);
    }
    
    // ...
}
  • NullHandling: 특정 컬럼의 값이 null일 경우의 순서를 지정(기본값: Default)
    • Default: 데이터베이스 기본 설정에 따라 null 순서가 결정됨
    • NullsFirst: null일 경우 첫 번째로 이동
    • NullsLast: null일 경우 마지막으로 이동

OrderSpecifier 생성자를 이용해 작성한 메서드는 다음과 같다.

class QueryDslUtil {

    companion object {
        fun getOrderSpecifier(qEntity: EntityPathBase<*>, sort: Sort): Array<OrderSpecifier<*>> {
            val orders = arrayListOf<OrderSpecifier<*>>()

            sort.forEach {
                orders.add(
                    OrderSpecifier(
                        if (it.isAscending) Order.ASC else Order.DESC,
                        PathBuilder(qEntity.type, qEntity.metadata)
                            .get(it.property) as Expression<out Comparable<*>> // OrderSpecifier<T extends Comparable>
                    )
                )
            }

            return orders.toTypedArray()
        }
    }
}
  1. 정렬할 Q타입의 클래스 객체(예: QTodo.todo)와 정렬에 대한 값을 가지고 있는 Pageable sort를 파라미터로 받는다.
  2. OrderSpecifier 객체를 저장할 리스트를 생성한다.
  3. Sort 객체에 포함된 모든 정렬 규칙에 대해 반복문을 수행하며 리스트에 값을 저장한다.
  4. 저장되는 값은 OrderSpecifier(정렬 기준, 정렬 기준 컬럼의 Path)이다.
  5. OrderSpecifier 객체가 담긴 리스트를 배열로 변환해 반환한다.

배열로 반환할 때의 주의점

  • toArray: Array<Any?>타입의 배열을 반환
    • orders.toArray() as Array<OrderSpecifier<*>>와 같이 타입 캐스팅을 하는 경우 ClassCastException 발생
  • toTypedArray: 컴파일 시점에 요소의 타입 정보를 사용해 특정 타입의 배열을 생성

orderBy가 파라미터로 받는 값은 단일 값이거나 가변 인자이다.

그러므로 전개 연산자(*)를 사용해 orderBy에 값을 넣어준다.

override fun search(searchType: SearchType, keyword: String, pageable: Pageable): Page<Todo> {

    // ...
    
    val contents = queryFactory.selectFrom(todo)
        .where(todo.isDeleted.isFalse.and(where))
        .orderBy(*QueryDslUtil.getOrderSpecifier(todo, pageable.sort))
        .offset(pageable.offset)
        .limit(pageable.pageSize.toLong())
        .fetch()

    return PageImpl(contents, pageable, totalCount)
}

 

서버를 실행해 다음과 같이 정렬 조건을 추가하여 요청을 보내면 정렬된 데이터를 확인할 수 있다.

 

서버 로그에서도 정렬 조건에 맞게 쿼리가 수행되는 것을 확인할 수 있다.

 

 

참고 자료

 

'JPA' 카테고리의 다른 글

Query DSL을 사용하기 위한 설정  (0) 2024.07.03