[QueryDSL] 정렬 (동적 정렬)

반응형

Introduction

보통 데이터를 정렬하고 싶을때 SQL 의 OrderBy를 사용하거나  프론트 테이블이 받은 행의 데이터를 원하는 기준으로 정렬한다. 보통의 웹사이트의 게시글들은 최신이 위로 오거나, 특정 테이블 헤더를 눌렀을 경우 그 데이터를 기준으로 정렬을 한다던지 말이다.

 

'만약 페이지네이션이 걸려 있는 페이지에서 '제목'이라는 헤더를 눌렀을 경우 데이터는 어떻게 정렬되어야 할까?'

 

기존의 조회된  첫 번째 페이지의 데이터만 제목을 기준으로 정렬이 되면 될까 ? 
아니면 모든 데이터를 기준으로 제목이 정렬되어야 할까 ? 물론 사람마다 원하는 바가 다르겠지만,
만약 후자라면 프론트 테이블에서 지원하는 정렬로는 해결할 수가 없기 때문에 결국 다시 서버로 요청을 해야한다.
그렇기 때문에 특정 정렬 조건을 서버에서 QueryDSL의  PathBuilder를 사용해 적용 했던 방법을 포스팅 하려고 한다.

정렬(QueryDSL)

 

1. 기본적인 정렬 방법

- QType Object Path를 기준으로 정렬을 체이닝한다. ','로 복수의 정렬을 요청할 수 있다.

jpaQueryFactory.
    select(member).
    from(member).
    orderBy(member.id.desc(), member.name.asc()).
    fetch();

 

 

2. 요청에 의한 기본적인 다수 조건 정렬 방법

- OrderSpecsifier[] 를 사용해 OrderDTO 객체에 컬럼명, 정렬방식의 구분 방법 등을 담아서 서버에서 처리한다. 
(예시에선 정렬방식 구분을 String 으로 처리하였고. Order 타입의 값을 받기위해 Enum 을 사용하는 것을 추천한다.)

public List<Member> findAll(OrderDTO order){
    List<OrderSpecifier> orderSpecifiers = new ArrayList<>();
    if(order.getId!=null){
    	// order 로 받은 필드의 값에 따라서 orderSpecifiers 에 add
        orderSpecifiers.add(new OrderSpecifier(order.getSortNameById().equals("desc")? Order.DESC:Order.ASC), member.id);
    }
    if(StringUtils.hasText(order.getName)){
        orderSpecifiers.add(new OrderSpecifier(ORDER_STATUS.of(order.getSortNameByName()), member.name);
    }

    JPAQuery<Member> members = jpaQueryFactory.
            select(member).
            from(member).
            orderBy(orderSpecifiers.toArray(new OrderSpecifier[orderSpecifiers.size()])).
            fetch();
    return members;
}

 

이렇게 처리할 경우 정렬 조건의 수에 따라서 많은 량의 반복적인 코드가 필요하다.

 

 

3. PathBuilder 를 사용한 동적 표현식 활용 정렬

- PathBuilder를 통해 String 으로 Entity 의 필드에 접근이 가능한 점을 이용해 String으로 정렬할 필드와 정렬방식을 받아서 처리한다.

PathBuilder<T> entity = new PathBuilder(Member.class, "member")
entity.get("엔티티 객체 필드 이름");
// ex) entity.get("id") -> member.id

List<OrderSpecifier> orderSpecifiers = new ArrayList<>();
// 정렬 방식 add
orderSpecifiers.add(new OrderSpecifier(Order.DESC, entity.get("id")));

jpaQueryFactory.
    select(member).
    from(member).
    orderBy(orderSpecifiers.toArray(new OrderSpecifier[orderSpecifiers.size()])).
    fetch();

 

활용

조회하는 도메인 Repository 에서 정렬을 원하는 컬럼과 정렬방식을 , 로 구분하는 String 값을 검색 조건과 함께 Condition으로 받고, 그 값과 함께 해당 도메인 PathBuilder 타입을 전달인자로 넘겨서 원하는 값의 OrderSpecSifier 배열을 받아서 사용한다.

public List<Member> findAll(SearchCondition condition){
	OrderSpecifier[] order = getOrder(condition.getSort(), new PathBuilder(Member.class, "member"));
    return jpaQueryFactory.
                select(member).
                from(member).
                orderBy(order).
                fetch();
}

public OrderSpecifier[] getOrder(String sortOrder, PathBuilder<T> entity) throws Exception {
    List<OrderSpecifier> orderSpecifiers = new ArrayList<>();
    // 특별한 sortOrder 가 없을 경우 ID를 기준으로 DESC 를 기본으로 한다.
    if(sortOrder==null){
        orderSpecifiers.add(new OrderSpecifier(Order.DESC, entity.get("id")));
    }
    if(StringUtils.hasText(sortOrder)){
        try {
            // sortOrder를 ','를 기준으로 split 을 진행해 entityField 와 orderValue 로 orderSpecifier 를 생성한다.
            // ex) sortOrder = id,desc,name,asc,age
            String[] orderArray = sortOrder.split(",");
            for (int i = 0; i < orderArray.length; i += 2) {
                String entityField = orderArray[i];
                // ','로 구분하는 기준에 필드 이름까지만 있고, 정렬 기준이 없을 경우 (위 예시의 age) 해당 entityField 를 DESC 로 설정해준다.
                Order orderValue = orderArray.length > i + 1 ? ORDER.of(orderArray[i + 1]) : Order.DESC;
                orderSpecifiers.add(new OrderSpecifier(orderValue, entity.get(entityField)));
            }
        }catch (Exception e){
            throw new CustomSortOrderException(HttpStatus.BAD_REQUEST, "정렬 형식이 올바르지 않습니다.");
        }
    }

    return orderSpecifiers.toArray(new OrderSpecifier[orderSpecifiers.size()]);
}

 

모든 Repository에서 조회시에 해당 메소드를 사용한다면 동일한 포맷으로 여러 엔티티에 동일하게 동적 정렬을 적용할 수 있다. 

반응형