spring

페이징처리할때 수동으로 처리하면서 공부한 내용

seungmin576 2025. 3. 5. 15:10

페이징 처리시 jpaRepository 가 제공하는 findAll에 Pageable 객체를 넣어주면 알아서 페이징처리하지만 이런 경우 필터링하여 원하는 결과를 얻을 수가 없기 때문에 필터링을 먼저한 후 수동으로 페이징처리를 해줘야 한다.

@Transactional(readOnly = true)
    public StorePageResponseDto findAll(int page, int pageSize, String name, StoreCategory category) {

        List<Store> storeList = storeRepository.findAllByNameAndCategory(name, category);
        List<Store> filteredStores = storeList.stream()
                .filter(store -> !store.getStatus().equals(StoreStatus.SHUTDOWN))
                .toList();  //페이징처리하기전에 폐업한 가게 필터링을 먼저해야함

        int pageNum = (page > 0) ? page - 1 : 0;    //사람한테는 1이 시작이지만 jpa가 제공하는 page는 시작 번호는 0번부터임
        Pageable pageable = PageRequest.of(pageNum, pageSize, Sort.by(Sort.Direction.ASC, "minimumPrice"));

        int totalElements = filteredStores.size();  //전부 몇개인지
        int start = (int) pageable.getOffset(); //시작 번호
        int end = Math.min((start + pageable.getPageSize()), totalElements);    //끝 번호
        List<Store> pageContent = filteredStores.subList(start, end);

        Page<Store> storePage = new PageImpl<>(pageContent, pageable, totalElements);	//필터링된 가게를 여기서 페이징 처리
        int currentPage = storePage.getNumber() + 1;	//현재 페이지 번호 사람은 1이 시작이니까 + 1
        int totalPages = storePage.getTotalPages(); //총 페이지 번호 

        List<StoreResponseDto> stores = storePage.stream()
                .map(store -> new StoreResponseDto(
                        store.getId(),
                        store.getTelephoneNumber(),
                        store.getAddress(),
                        store.getContents(),
                        store.getName(),
                        store.getOpenTime(),
                        store.getCloseTime(),
                        store.getMinimumPrice(),
                        store.getCategory(),
                        store.getStatus()
                )).toList();

        return new StorePageResponseDto(stores, currentPage, totalPages, totalElements);
    }

 

 

int end = Math.min((start + pageable.getPageSize()), totalElements);

이 부분의 코드는 현재 페이지의 끝 인덱스를 계산하기 위해 사용됩니다. 구체적으로 설명하면:

  1. start는 현재 페이지의 시작 인덱스를 나타냅니다. 이는 pageable.getOffset()를 통해 구해집니다.
  2. pageable.getPageSize()는 페이지당 요소의 수를 나타냅니다.

start + pageable.getPageSize()는 현재 페이지의 마지막 요소가 포함된 인덱스를 계산합니다. 예를 들어, 페이지당 요소가 10개이고 시작 인덱스가 20이라면, 마지막 요소는 29번째 인덱스가 됩니다.

하지만 이 계산된 끝 인덱스가 전체 요소 수(totalElements)를 넘지 않도록 하기 위해 Math.min() 함수를 사용합니다. 이 함수는 두 값을 비교하여 더 작은 값을 반환합니다. 따라서 계산된 끝 인덱스가 전체 요소 수를 넘을 경우 전체 요소 수가 끝 인덱스로 설정됩니다.

예시로 설명하자면:

  • 전체 요소 수(totalElements)가 25개이고, 페이지당 10개씩, 3번째 페이지를 요청했다고 가정합니다.
    • start = 20 (2 * 10)
    • end = Math.min(20 + 10, 25) -> end = Math.min(30, 25) -> end = 25

이렇게 해서 끝 인덱스가 25로 설정되며, 현재 페이지에 포함될 요소의 범위가 20부터 24까지로 제한됩니다.

 

 

 

 

List<Store> pageContent = filteredStores.subList(start, end);

이 코드는 필터링된 전체 리스트에서 특정 범위의 요소들을 추출하기 위해 사용됩니다. 구체적으로 설명하면:

  1. filteredStores: 이 리스트는 앞서 필터링된 전체 Store 객체의 목록입니다.
  2. start: 이 변수는 현재 페이지의 시작 인덱스를 나타냅니다.
  3. end: 이 변수는 현재 페이지의 끝 인덱스를 나타냅니다.

subList(start, end) 메서드는 filteredStores 리스트에서 start 인덱스부터 end 인덱스까지의 부분 리스트를 반환합니다.

인덱스는 0부터 시작하며, end 인덱스는 포함되지 않습니다.

따라서, start 인덱스부터 end - 1 인덱스까지의 요소들이 반환됩니다.

예를 들어, filteredStores 리스트가 다음과 같다고 가정해봅시다:

java
List<Store> filteredStores = Arrays.asList(store1, store2, store3, store4, store5);

만약 start = 1이고 end = 4라면, subList(start, end)는 다음과 같은 리스트를 반환합니다:

java
List<Store> pageContent = filteredStores.subList(1, 4); // [store2, store3, store4]

즉, store2, store3, store4 객체들이 포함된 부분 리스트를 반환합니다.

이렇게 하면 현재 페이지에 해당하는 요소들만 추출할 수 있게 됩니다.

 

 

 

위에서 사용한 레포지터리 메서드

public interface StoreRepository extends JpaRepository<Store, Long> {
    //카테고리가 같은지 찾음
    //이름이 비어있지 않으며 이름이 like name 조건에 해당하면 우선순위 0 아니면 1로 이름 오름차순정렬
    @Query("SELECT s FROM Store s WHERE s.category = :category " +
            "ORDER BY CASE WHEN :name IS NOT NULL AND s.name LIKE %:name% THEN 0 ELSE 1 END, s.name ASC")
    List<Store> findAllByNameAndCategory(@Param("name") String name, @Param("category") StoreCategory category);
}

가게의 이름을 우선으로 비슷한걸 찾고 없으면 카테고리 찾는 쿼리문을 사용했다.