[Spring] 세상 간단한 Pagination - Controller에서 Pageable 객체 받아 사용하기

Kim Dae Hyun·2022년 3월 10일
0

Controller에서 쿼리 스트링으로 전달되는 페이징에 대한 정보를 Pageable 객체에 매핑시켜서 사용할 것 입니다.

제 기준 가~장 간단한 페이징 기법? 입니다.


📌 Controller 코드 작성

@RequestMapping(value = "/expenses")
@RequiredArgsConstructor
@RestController
public class ExpenseController {

    private final ExpenseService expenseService;

    @GetMapping
    public Page<ExpenseResponseDto> getAllExpenses(Pageable pageable) {
        return expenseService.getAllExpenses(pageable);
    }
}    

page, size, sort 등 페이징 및 정렬에 필요한 파라미터를 Pagable 객체에 매핑시키고 그대로 Service 계층으로 넘겨주고 있습니다.


📌 Service 코드 작성

ControllerRepository의 인터페이스 역할만을 수행하고 있습니다.
Controller에서 직접 Repository를 주입받아 사용하는 것도 큰 무리는 없어보이지만 다른 비즈니스 로직과 함께 두기 위해 Service 계층으로 내렸습니다.

@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class ExpenseServiceImpl implements ExpenseService {

    private final ExpenseRepository expenseRepository;

    @Override
    public Page<ExpenseResponseDto> getAllExpenses(Pageable pageable) {
        return expenseRepository.findAllExpense(pageable);
    }
}    

📌 Repository 코드 작성

DTO로 직접 조회하는 방법을 사용했습니다.
Repository 계층에서 Service 계층의 DTO를 참조하는 것이 설계상 별로인 것처럼 보이지만 Entity 타입으로 조회하고 다시 DTO로 변환하는 작업을 한 번에 처리할 수 있다는 장점이 있어서 아래와 같은 방법을 사용했습니다.

일단 DTO로 조회한 것은 무시하고..
Pageable 인자로 받아서 Page 타입으로 반환하는 것이 전부입니다.

public interface ExpenseRepository extends JpaRepository<Expense, Long> {

    @Query("select new com.dhk.expensetrackerapi.service.dto.response.ExpenseResponseDto(e.id, e.name, e.description, e.amount, e.category, e.date, e.createdAt, e.updatedAt) from Expense e ")
    Page<ExpenseResponseDto> findAllExpense(Pageable pageable);
}

성능을 떠나서 이런 편리함과 간결함이 Spring data JPA를 사용하는 가장 큰 이유가 아닐까 라고 생각합니다.


📌 Postman API 테스트

요청 URL: /expenses?page=0&size=2

{
    "content": [
        {
            "id": 1,
            "name": "수도세",
            "description": "수도세",
            "amount": 9000,
            "category": "청구서",
            "date": "2022-03-06",
            "createdAt": "2022-03-10T13:22:09",
            "updatedAt": "2022-03-10T13:22:09"
        },
        {
            "id": 2,
            "name": "전기세",
            "description": "전기세",
            "amount": 32000,
            "category": "청구서",
            "date": "2022-03-06",
            "createdAt": "2022-03-10T13:22:09",
            "updatedAt": "2022-03-10T13:22:09"
        }
    ],
    "pageable": {
        "sort": {
            "empty": true,
            "sorted": false,
            "unsorted": true
        },
        "offset": 0,
        "pageSize": 2,
        "pageNumber": 0,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalPages": 3,
    "totalElements": 5,
    "size": 2,
    "number": 0,
    "sort": {
        "empty": true,
        "sorted": false,
        "unsorted": true
    },
    "first": true,
    "numberOfElements": 2,
    "empty": false
}

요청 URL: /expenses?page=0&size=2&sort=amount

페이징 + amount 필드 기준 오름차순(default)

{
    "content": [
        {
            "id": 5,
            "name": "name",
            "description": "description",
            "amount": 1000,
            "category": "category",
            "date": "2022-03-09",
            "createdAt": "2022-03-10T13:46:29",
            "updatedAt": "2022-03-10T13:46:29"
        },
        {
            "id": 1,
            "name": "수도세",
            "description": "수도세",
            "amount": 9000,
            "category": "청구서",
            "date": "2022-03-06",
            "createdAt": "2022-03-10T13:22:09",
            "updatedAt": "2022-03-10T13:22:09"
        }
    ],
    "pageable": {
        "sort": {
            "empty": false,
            "sorted": true,
            "unsorted": false
        },
        "offset": 0,
        "pageSize": 2,
        "pageNumber": 0,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalPages": 3,
    "totalElements": 5,
    "size": 2,
    "number": 0,
    "sort": {
        "empty": false,
        "sorted": true,
        "unsorted": false
    },
    "first": true,
    "numberOfElements": 2,
    "empty": false
}

요청 URL: /expenses?page=0&size=2&sort=amount,desc

페이징 + amount 필드 기준 내림차순

{
    "content": [
        {
            "id": 3,
            "name": "관리비",
            "description": "관리비",
            "amount": 60000,
            "category": "청구서",
            "date": "2022-03-09",
            "createdAt": "2022-03-10T13:22:09",
            "updatedAt": "2022-03-10T13:22:09"
        },
        {
            "id": 2,
            "name": "전기세",
            "description": "전기세",
            "amount": 32000,
            "category": "청구서",
            "date": "2022-03-06",
            "createdAt": "2022-03-10T13:22:09",
            "updatedAt": "2022-03-10T13:22:09"
        }
    ],
    "pageable": {
        "sort": {
            "empty": false,
            "sorted": true,
            "unsorted": false
        },
        "offset": 0,
        "pageSize": 2,
        "pageNumber": 0,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalPages": 3,
    "totalElements": 5,
    "size": 2,
    "number": 0,
    "sort": {
        "empty": false,
        "sorted": true,
        "unsorted": false
    },
    "first": true,
    "numberOfElements": 2,
    "empty": false
}

sort를 쿼리 스트링으로 넘기는 부분이 신기합니다..

?sort=필드,desc

오호 ...


Page 타입으로 리턴되는 값에 불필요한 값이 너무 많다면 content 부분과 필요한 부분만으로 응답 DTO를 구성하고 List 로 반환하는 것도 좋은 방법이라 생각됩니다

:)

profile
좀 더 천천히 까먹기 위해 기록합니다. 🧐

0개의 댓글