토이 프로젝트 배우게 된 것들 & 오류 해결🐰

JPA | QueryDsl orderBy(정렬) 동적으로 사용하는 방법

j_estory 2023. 1. 21. 17:17

리뷰 리스트를 조회하는 API 내에서,

- 최근 등록 순

- 별점 높은 순

- 별점 낮은 순

으로 데이터를 정렬했어야 했다. 

정적으로 고정된 정렬이 아닌, 파라미터 값에 따라 동적으로 정렬이 되어야 했다.

 

queryDsl 에서 동적 정렬을 위한 OrderSpecifier 클래스를 사용해보려고 한다!

 

일단 해당 클래스의 필드들을 살펴보면 아래와 같다.

OrderSpecifier

클래스의 멤버들을 살펴 보면 해당 클래스의 생성자를 사용하여 생성할 수 있는데,

파라미터 변수들로는 Order enum, target 객체, NullHandling enum이 존재한다.

 

- order Enum은 아래와 같이 구성되며, 상황에 맞게 내림차순, 오름차순을 적용해주면 될 것 같다.

public enum Order {
    /**
     * Ascending order
     */
    ASC,
    /**
     * Descending order
     */
    DESC;

}

 

- NullHandling Enum은 null인 컬럼에 대해 정렬을 어떻게 처리해 줄 것인지에 대한 옵션이다.

정의되어 있는 Enum 값을 보게 되면 아래와 같다.

public enum NullHandling { Default, NullsFirst, NullsLast }

NullsFirst 
- null에 대한 값들을 앞쪽으로 정렬할 것인지

NullsLast
- null에 대한 값들을 뒤쪽으로 정렬할 것인지

OrderSpecifier 클래스를 사용하여 paramter에 따른 정렬을 동적으로 만들어보았다.

 @Override
    public Page<Review> findAllReviewByRoomList(List<Long> roomseqList, Pageable pageable, ReviewDto.ReqRes req) {
        List<Review> fetch = jpaQueryFactory.selectFrom(QReview.review)
                .join(QReview.review.room, QRoom.room).fetchJoin()
                .where(QReview.review.room.roomSeq.in(roomseqList))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .orderBy(reviewSort(req))
                .fetch();

        JPAQuery<Long> countQuery = jpaQueryFactory.select(QReview.review.count())
                .from(QReview.review)
                .where(QReview.review.room.roomSeq.in(roomseqList));

        return PageableExecutionUtils.getPage(fetch, pageable, countQuery::fetchOne);
    }
    
    
    // reviewSort() 메서드
    private OrderSpecifier<?> reviewSort(ReviewDto.ReqRes req) {
        if (!ObjectUtils.isEmpty(req) && !ObjectUtils.isEmpty(req.getFilterType())) {
            switch (req.getFilterType()) {
                case "recentDate":
                    return new OrderSpecifier(Order.DESC, QReview.review.createdAt);
                case "highScore":
                    return new OrderSpecifier(Order.DESC, QReview.review.avgStartScore);
                case "lowScore":
                    return new OrderSpecifier(Order.ASC, QReview.review.avgStartScore);
            }
        }
        return new OrderSpecifier(Order.DESC, QReview.review.createdAt);
    }

기본 정렬은 리뷰 등록일자가 최신순으로 되어 있으므로, default 값으로 설정해 준다.

 

-------------------- 추가된 내용 --------------------

order by에 대해서 다중으로 적용해야 하는 로직이 추가되어야 했다.

예를 들어 위의 로직은 order by created_at desc 하나의 조건이였다면, 

추가되는 로직에서는 order by best_yn desc, created_at desc 와 같이 정렬조건이 다중으로 적용되어야 했다.

 

이를 OrderSpecifier에서 적용을 하기 위해서

OrderSpecifier[]

배열을 사용하여 정렬 조건들을 넣어주면 되었다.

 

private List<OrderSpecifier<?>> reviewSort(ReviewDto.ReqRes req) {
    List<OrderSpecifier<?>> orderSpecifierList = new ArrayList<>();

    if (ReviewDto.FilterType.LOWSCORE.getName().equals(req.getFilterType())) {
        orderSpecifierList.add(new OrderSpecifier<>(Order.ASC, QReview.review.avgStartScore));
    } else if (ReviewDto.FilterType.HIGHSCORE.getName().equals(req.getFilterType())) {
        orderSpecifierList.add(new OrderSpecifier<>(Order.DESC, QReview.review.avgStartScore));
    } else {
        orderSpecifierList.add(new OrderSpecifier<>(Order.DESC, QReview.review.bestYn));
        orderSpecifierList.add(new OrderSpecifier<>(Order.DESC, QReview.review.createdAt));
    }
    return orderSpecifierList;
}


// 적용 쿼리
List<Review> fetch = jpaQueryFactory.selectFrom(QReview.review)
    .join(QReview.review.room, QRoom.room).fetchJoin()
    .where(QReview.review.room.roomSeq.in(roomseqList))
    .offset(pageable.getOffset())
    .limit(pageable.getPageSize())
    .orderBy(reviewSort(req).toArray(OrderSpecifier[]::new))
    .fetch();

몇개의 정렬조건이 들어갈지 모르니 위와 같이 ArrayList를 통해서 각 case마다 정렬조건을 넣어주고,

query문 내에 해당 메소드를 불러올 때, ArrayList > Array 로 변경을 해주면 

OrderSpecifier을 사용하여 다중 정렬 조건을 적용할 수 있다.