본문 바로가기

Today I Learned

페이징 처리

ㅋㅋㅋ 고대 선배들이 남겨준 페이징 처리

사실 회사에서는 페이징 처리는 이미 구현이 되어있어서 고민을 해본 적이 없었다.

 

 

페이징 처리

jpa로 페이징 처리를 할때는 Page라는 객체를 쓰면 간단해진다.

그러나 이것은 단점을 가지고 있는데, count 쿼리를 사용하기때문에 총 개수가 많아지면 점점 느려진다. 왜냐면 전체 개수를 세야하기 때문이다.

 

그래서 그것의 대안으로 어떻게 하는가?

1) 더보기 방식, 아래를 스크롤하면 현재 위치의 index에서 다음 10개를 가져오는 방식

2) Page 쿼리를 수정해서 위에처럼 현재 index에서 10개를 가져오는 방식으로 수정

 

현재 나는 순수 jpa는 타자 칠게 많아서 눈으로 읽어주기만하고

queryDSL로 적용해보려고한다.

 

1) 전체 유저의 조회 + 페이징처리

2) 찾기 기능(키워드) + 페이징 처리

 

이렇게 2개의 api를 구현하고 싶어서 김영한님이 알려주신 QueryDSL을 적용했는데 아뿔싸

내가 만든 SearchDTO를 적용하자니 코드가 복잡해졌다. 수정 수정이 필요하다!!

 

더보기

SearchDTO 는 인터페이스 적용을 아직 하지 않음

package com.coopang.user.presentation.request;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class SearchRequestDto {
    private String keyword; // 회원 검색 키워드
    private String sortBy;  // 정렬 기준 (createdAt, updatedAt 등)
    private Integer page;       // 페이지 번호
    private Integer size;       // 페이지 크기

    // 페이지 크기 유효성 검사를 수행하고, 기본값을 반환하는 메서드
    // 페이지 크기를 10, 30, 50으로 제한
    public int getValidatedSize() {
        return (size == null) ? 10 : (size <= 10 ? 10 : (size <= 30 ? 30 : 50));
    }
    // 페이지 번호 기본값 설정 메서드
    public int getValidatedPage() {
        return (page != null && page >= 0) ? page : 0;
    }

    // 정렬 기준 기본값 설정 메서드
    public String getValidatedSortBy() {
        return (sortBy != null && !sortBy.isEmpty()) ? sortBy : "createdAt";
    }
}

 

 

굴러가긴 하나 정리 안된 막써 QueryDSL

package com.coopang.user.infrastructure.repository;


import static com.coopang.user.domain.entity.user.QUserEntity.userEntity;

import com.coopang.user.application.enums.UserRoleEnum;
import com.coopang.user.domain.entity.user.UserEntity;
import com.coopang.user.presentation.request.UserSearchCondition;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Objects;

@Repository
public class UserRepositoryCustomImpl implements UserRepositoryCustom {

    private final JPAQueryFactory queryFactory;

    public UserRepositoryCustomImpl(EntityManager em) {
        this.queryFactory = new JPAQueryFactory(em);
    }

    @Override
    public Page<UserEntity> search(UserSearchCondition condition, Pageable pageable) {
        List<UserEntity> users = queryFactory
                .select(userEntity)
                .from(userEntity)
                .where(
                        usernameEq(condition.getUserName()),
                        emailEq(condition.getEmail()),
                        roleEq(condition.getUserRole())
                )
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .orderBy(getOrderSpecifier(pageable))
                .fetch();

        long total = queryFactory
                .select(userEntity.count())
                .from(userEntity)
                .where(
                        usernameEq(condition.getUserName()),
                        emailEq(condition.getEmail()),
                        roleEq(condition.getUserRole())
                )
                .fetchOne();

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

    private OrderSpecifier<?>[] getOrderSpecifier(Pageable pageable) {
        if (pageable.getSort().isEmpty()) {
            return new OrderSpecifier<?>[] {userEntity.createdAt.desc()}; // 기본 정렬 기준
        }

        // 정렬 조건을 직접 생성합니다.
        OrderSpecifier<?>[] orderSpecifiers = pageable.getSort().stream()
                .map(order -> {
                    if (order.getProperty().equals("createdAt")) {
                        return order.isAscending() ? userEntity.createdAt.asc() : userEntity.createdAt.desc();
                    }
                    if (order.getProperty().equals("updatedAt")) {
                        return order.isAscending() ? userEntity.updatedAt.asc() : userEntity.updatedAt.desc();
                    }
                    // 다른 정렬 기준 추가 가능
                    return null;
                })
                .filter(Objects::nonNull)
                .toArray(OrderSpecifier[]::new);

        // 정렬 조건이 없는 경우 기본 정렬 기준을 사용합니다.
        return orderSpecifiers.length > 0 ? orderSpecifiers : new OrderSpecifier<?>[] {userEntity.createdAt.desc()};
    }

    private Predicate emailEq(String email) {
        return StringUtils.hasText(email) ? userEntity.email.eq(email) : null;
    }

    private Predicate usernameEq(String userName) {
        return StringUtils.hasText(userName) ? userEntity.username.eq(userName) : null;
    }

    private Predicate roleEq(String userRole) {
        return StringUtils.hasText(userRole) ? userEntity.role.eq(UserRoleEnum.valueOf(userRole)) : null;
    }
}