본문 바로가기
문제 해결 기록

[QueryDSL] 페이징 직접 처리하기

by 타태 2022. 4. 12.



2022.04.09 - [문제 해결 기록] - [IntelliJ + Maven + QueryDSL] Failed to execute goal com.mysema.maven:apt-maven-plugin:1.1.3:process 해결

 

[IntelliJ + Maven + QueryDSL] Failed to execute goal com.mysema.maven:apt-maven-plugin:1.1.3:process 해결

2022.03.23 - [실전 공부] - [Spring Boot] LogBack.xml to Java Config Bean [Spring Boot] LogBack.xml to Java Config Bean 2022.03.21 - [문제 해결 기록] - [Spring boot X Spring Security] CORS 설정하기..

ktae23.tistory.com



QeuryDSL로 조건부 검색 + 페이징을 할 경우 이런 예제를 많이 만나게 된다.

@Override
public Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable) {

    QueryResults<Item> results = queryFactory
            .selectFrom(QItem.item)
            .where(regDtsAfter(itemSearchDto.getSearchDateType()),
                    searchSellStatusEq(itemSearchDto.getSearchSellStatus()),
                    searchByLike(itemSearchDto.getSearchBy(),
                            itemSearchDto.getSearchQuery()))
            .orderBy(QItem.item.id.desc())
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .fetchResults();

    List<Item> content = results.getResults();
    long total = results.getTotal();

    return new PageImpl<>(content, pageable, total);
}

출처 - 로드북 (출판사) 깃허브



그리고 자연스레 예제에서 사용한 fetchCount와 fetchResult가 지원 중단 예정임을 알게 된다.

중단 예정이라면서 버그 픽스에 올라온건 뭐지..? 다시 되나?



따라서 이를 대체하고자 아래처럼 일단 조회 후 사이즈를 재거나 카운트 쿼리를 별도로 날리는 방법을 사용하게 된다.

final List<Item> itemList = queryFactory
        .selectFrom(item)
        .where(regDtsAfter(itemSearch.getSearchDateType()),
                searchSellStatusEq(itemSearch.getSearchSellStatus()),
                searchByLike(itemSearch.getSearchBy(), itemSearch.getSearchQuery()))
        .orderBy(item.id.desc())
        .offset(pageable.getOffset())
        .limit(pageable.getPageSize())
        .fetch();

final int total_1 = itemList.size();

final Long total_2 = queryFactory
        .select(item.count())
        .from(item)
        .where(regDtsAfter(itemSearch.getSearchDateType()),
                searchSellStatusEq(itemSearch.getSearchSellStatus()),
                searchByLike(itemSearch.getSearchBy(), itemSearch.getSearchQuery()))
        .distinct()
        .fetchOne();

 


그런데 괜히 한번 페이징처리를 직접 해볼까? 싶은 충동이 일었다.
그래서 아래처럼 쿼리는 조회만 남겼다.

@Override
public List<Item> getAdminItemWithSearchCondition(ItemSearch itemSearch, Pageable pageable) {
           return queryFactory
            .selectFrom(item)
            .where(regDtsAfter(itemSearch.getSearchDateType()),
                    searchSellStatusEq(itemSearch.getSearchSellStatus()),
                    searchByLike(itemSearch.getSearchBy(), itemSearch.getSearchQuery()))
            .orderBy(item.id.desc())
            .fetch();
}


그리고 페이징처리를 하는 메서드를 구현했다.

public static <T> Page<T> pagination(List<T> entityList, Pageable pageable) {
    List<T> content = new ArrayList<>();
    long total = entityList.size();

    // 빈 객체가 아닐 경우
    if (isNotNullOrEmpty(entityList)) {
        int pageSize = pageable.getPageSize();
        int pageNumber = pageable.getPageNumber() + 1;

        int offset = (pageSize * pageNumber) - pageSize;
        int limit = pageSize * pageNumber;


        // 마지막 페이지인 경우 종료 인덱스
        if (limit > entityList.size()) {
            limit = entityList.size();
        }
        content = entityList.subList(offset, limit);
    }
    return new PageImpl<>(content, pageable, total);
}


당연히 조회 할때부터 걸러서 가져오는게 더 나을 수 있고 사실 이렇게 구태여 분리해야할 이유는 전혀 없다.
그냥 해보고 싶어서 했고, 시행착오를 많이 겪어야만 했다.
정말 지저분하고 복잡하게 짠것을 줄이고 줄여 겨우 이렇게나 깔끔하게 구현이 가능함을 깨달았다.

무지성으로 키보드부터 두드리는 것이 아니라 구현하려는 기능의 요건을 파악하는 것이 훨씬 중요하다는걸 또 느낀다.



** 번외 : 만들어두고 자주 사용하는 null 체크 메서드

public class Utils {
    public static  <T> boolean isNullOrEmpty(T t) {
        if (t instanceof Map) {
            return ((Map<?, ?>) t).isEmpty();
        }
        if (t instanceof List) {
            return ((List<?>) t).isEmpty();
        }
        if (t instanceof Set) {
            return ((Set<?>) t).isEmpty();
        }
        return t == null;
    }

    public static  <T> boolean isNotNullOrEmpty(T t) {
        if (t instanceof Map) {
            return !((Map<?, ?>) t).isEmpty();
        }
        if (t instanceof List) {
            return !((List<?>) t).isEmpty();
        }
        if (t instanceof Set) {
            return !((Set<?>) t).isEmpty();
        }
        return t != null;
    }

    private Utils(){}
}
반응형

댓글