Pagination 이란 데이터가 대량으로 있을 때 이를 일정하게 나누어서 페이지에 필요한 만큼만 보여주는 것을 말한다.
Spring에서 제공하는 페이지네이션 API를 사용하면 보다 쉽게 구현할 수 있다.
페이지 번호가 page, 한 페이지에 담기는 데이터 개수가 size 인 경우 쿼리문을 이용하면
[SELECT * FROM table명 LIMIT (page-1)*size, size] 혹은 [SELECT * FROM table명 LIMIT size OFFSET (page-1)*size]
등의 구문을 사용해 데이터를 조회해야 한다.
이러한 페이지네이션은 스프링에서 Pageable, PageRequest 를 사용해 적용할 수 있다.
Pageable
아래 내용에 표기된것처럼 Pageable 은 페이지네이션을 위한 인터페이스이다.
페이지 번호, 페이지 크기 등 페이지네이션에 필요한 정보를 포함한다.
또한 Page<T> 인터페이스를 통해 페이징 결과를 반환한다.
PageRequest
Pageable을 구현한 클래스로 PageRequest가 있다.
Pageable을 구현한 AbstractPageRequest 클래스를 상속받으므로 page, size를 필드로 갖는다.
Page<T>
페이징 결과를 반환하는 인터페이스이다.
여러 명 회원 정보를 가지고 있는 Member 테이블의 데이터를 10개씩 내림차순으로 출력(리턴)하는 코드를 구현해 보자.
페이지의 크기 size(int)와 페이지 번호 page(int)를 파라미터로 받아서 사용한다고 가정하자.
1. 페이지 정보 클래스 정의
조회하려는 페이지의 정보가 pageInfo에 포함되어야 하므로, 페이지 정보 클래스를 정의해 준다.
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class PageInfo {
private int page;
private int size;
private int totalElements;
private int totalPages;
}
2. Repository 메서드 추가
Pageable 객체를 통해 Page <T> 데이터를 가져오기 위해 관련 메서드를 추가한다.
public interface MemberRepository extends CrudRepository<Member, Long> {
Optional<Member> findByEmail(String email);
// Pageable에 담긴 정보대로 페이지 조건을 설정하고, memberId를 기준으로 내림차순으로 정렬해주는 메서드
Page<Member> findAllByOrderByMemberIdDesc(Pageable pageable);
}
3. Service 메서드 수정
컨트롤러에서 메서드 호출이 되어 직접적으로 비즈니스 로직을 수행하는 곳은 Service 클래스이다. 매칭되는 메서드를 수정한다.
public Page<Member> findMembers(int page, int size) {
// page, size 를 파라미터로 받아서 PageRequest 객체를 생성 => PageRequest 의 of() 메서드 사용
// page는 0부터 시작하므로 -1 필요
PageRequest pageRequest = PageRequest.of(page-1, size);
// 생성된 pageRequest 객체를 사용해 findAllByOrderByMemberIdDesc() 호출
return memberRepository.findAllByOrderByMemberIdDesc(pageRequest);
}
4. Controller 메서드 수정
클라이언트의 조회 요청을 직접적으로 받아들이는 곳은 MemberController이다.
Get 요청을 받는 메서드 파라미터로 size와 page를 넣고 유효성 검사 조건을 넣어준다. (page와 size는 양의 정수여야 한다.)
또한 Member를 요소로 갖는 Page <Member>를 사용해 필요한 값을 가져올 수 있다.
@GetMapping
public ResponseEntity getMembers(@Positive @RequestParam int page,
@Positive @RequestParam int size) {
// 페이지 정보를 가져온다.
Page<Member> pageMember = memberService.findMembers(page, size);
PageInfo pageInfo = new PageInfo(page, size, (int) pageMember.getTotalElements(), pageMember.getTotalPages());
// List<DTO> 형태로 데이터를 가공한다.
// getContent() 메서드는 Slice<T> 인터페이스에 포함된 메서드로, 페이지 내용을 List<T> 로 리턴해준다.
// Page<T> 가 Slice<T> 를 상속받기 때문에 getContent()를 사용할 수 있다.
List<Member> members = pageMember.getContent();
List<MemberResponseDto> response = mapper.membersToMemberResponseDtos(members);
// 리턴되는 정보에 List<MemberResponseDto>와 PageInfo가 모두 포함되어야하므로 이 둘을 포함하는 새로운 ResponseDto 사용
return new ResponseEntity<>(new MemberPageResponseDto(response, pageInfo), HttpStatus.OK);
}
5. MemberPageResponseDto 정의
ResponseEntity에 멤버 정보와 페이지 정보를 담기 위해 새로운 DTO 객체를 정의해 준다.
@Getter
@AllArgsConstructor
public class MemberPageResponseDto <T>{
private T data;
private PageInfo pageInfo;
}
6. 결과 출력
데이터베이스에 회원 정보는 20개가 들어있으므로 1페이지, 페이지당 사이즈 10으로 조회했을 때 정상적으로 출력되는 것을 볼 수 있다.
(페이지 정보가 포함되어 있는지, 데이터가 memberId 기준 내림차순인지 등)
오늘은 하루종일 실습이 진행된 날이다. 어제 진행했던 데이터 액세스 계층 구현과는 다른 내용을 다뤘다.
처음 보는 Pageabe, PageRequest와 같은 개념을 사용해야 해서 흐름을 이해하는데 시간이 조금 걸렸다.
매번 느끼는 부분이지만 객체 타입에 따라서 사용 가능한 메서드들이 달라지기 때문에
어떤 타입인지 파악하고 사용 가능한 메서드의 리턴 타입은 어떻게 되는지 확실히 숙지해야겠다.
오늘 내가 작성한 코드가 최선의 코드는 아닐 거라는 생각이 든다. 앞으로 학습하면서 더 이해하기 쉽고 깔끔한 코드를 작성하고 싶다.
'부트캠프 개발일기 > Spring MVC' 카테고리의 다른 글
50일차: 엔티티간 연관관계 매핑 (0) | 2023.04.24 |
---|---|
49일차: JPA(Java Persistence API) (0) | 2023.04.21 |
47일차: Spring MVC(데이터 액세스 계층) (0) | 2023.04.19 |
46일차: Spring MVC(JDBC, Spring Data JDBC) (0) | 2023.04.18 |
45일차: Spring MVC(비즈니스적인 예외 던지기, throw) (0) | 2023.04.17 |