1. 페이징 paging 이란?
- 페이징 처리는 큰 데이터셋을 작은 '페이지'로 나누고, 각 페이지에는 고정된 수의 데이터 항목이 포함되어 있음
- 전체 데이터셋에서 일부 데이터만을 조회하여 애플리케이션의 성능을 향상시킬 수 있으며, 사용자 인터페이스에서 데이터를 페이지 단위로 표시가능
- 사용자 또는 애플리케이션은 한 번에 한 페이지의 데이터만 요청하며, 필요에 따라 다음 페이지나 이전 페이지의 데이터를 요청가능
2. 구현방법
(1) Repository 인터페이스 정의 및 PageRequest 를 사용하여 Pageable 객체화
PageRequest.of(int page int size, Sort sort, Direction direction, String ... props) :
(2) Pageable을 JpaRepository가 상속된 인터페이스의 메서드에 T(Entity)와 함꼐 파라미터로 전달 후, Page 반환
Page<User> usersByUserRole = userRepository
.findUsersByUserRole(UserRole.SELLER, pageDTO.toPageable());
3. Page 객체의 주요 메서드
- getContent(): 현재 페이지에 해당하는 데이터 리스트를 반환합니다.
- getTotalElements(): 전체 데이터 수를 반환합니다.
- getTotalPages(): 전체 페이지 수를 반환합니다.
- getNumber(): 현재 페이지 번호를 반환합니다.
- getNumberOfElements(): 현재 페이지에 있는 데이터 수를 반환합니다.
- hasNext(), hasPrevious(), isFirst(), isLast(): 페이징 네비게이션 관련 정보를 제공합니다.
4. Pageable 응답형식
{
"content": [
{"id": 1, "username": "User 0", "address": "Korea", "age": 0},
...
{"id": 5, "username": "User 4", "address": "Korea", "age": 4}
],
"pageable": {
"sort": {
"sorted": false, // 정렬 상태
"unsorted": true,
"empty": true
},
"pageSize": 5, // 페이지 크기
"pageNumber": 0, // 페이지 번호 (0번 부터 시작)
"offset": 0, // 해당 페이지의 첫번째 요소의 전체 순번 (다음 페이지에서는 5)
"paged": true,
"unpaged": false
},
"totalPages": 20, // 페이지로 제공되는 총 페이지 수
"totalElements": 100, // 모든 페이지에 존재하는 총 원소 수
"last": false, // 마지막 페이지 여부
"number": 0,
"sort": {
"sorted": false, // 정렬 사용 여부
"unsorted": true,
"empty": true
},
"size": 5, // Contents 사이즈
"numberOfElements": 5, // Contents 의 원소 수
"first": true, // 첫페이지 여부
"empty": false // 공백 여부
}
5. 페이지 반환 타입 (3가지)
Page<T> | 게시판 형태의 페이징, totalelement도 같이 조회함 |
Slice<T> | 더보기 형태의 페이징, 전체 요소 갯수 대신 offset 필드로 조회 (단, 성능이 안좋아서 limit+1로 사용) |
List<T> | 전체 목록보기 형태의 페이징, count 조회가 발생하지 않음 |
+) 전체 count 쿼리가 추가로 발생하는 Page<T> 보다는 List<T>가 대용량 처리할때 더 안정적
6. 페이징 정렬
Sort 클래스 / JpaSort / Query 이용
//pageable
Pageable pageable = PageRequest.of(pageNumber, pageSize, Sort.by("someField").ascending());
Page<MyEntity> resultPage = myRepository.findAll(pageable);
//Sort 클래스
Sort sort = Sort.by(Sort.Order.asc("field1"), Sort.Order.desc("field2"));
Pageable pageable = PageRequest.of(pageNumber, pageSize, sort);
Page<MyEntity> resultPage = myRepository.findAll(pageable);
//JpaSort
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.department.id = :departmentId")
List<User> findByDepartmentIdSortedByUserNameLength(
@Param("departmentId") Long departmentId,
Sort sort);
}
Sort sort = JpaSort.unsafe("LENGTH(u.name)");
List<User> users = userRepository.findByDepartmentIdSortedByUserNameLength(departmentId, sort);
7. PageDTO 클래스
- `currentPage`: 현재 페이지 번호
- `pageSize`: 한 페이지에 표시할 게시글의 수
- `totalElements`: 전체 게시글 수
- `totalPages`: 전체 페이지 수, `totalElements`를 `pageSize`로 나눈 후 올림하여 계산
- `dataList`: 현재 페이지에 표시할 데이터의 리스트
- `startPage`, `endPage`: 페이징 바에서 표시할 시작 페이지 번호와 끝 페이지 번호
- `hasPreviousPage`, `hasNextPage`: 이전 페이지 또는 다음 페이지가 존재하는지의 여부
### 계산 방법
1. **전체 페이지 수 계산**:
totalPages = (int) Math.ceil((double) totalElements / pageSize);
2. **시작 페이지와 끝 페이지 계산**:
- 페이징 바에서 몇 개의 페이지를 표시할지 결정한다.
예를 들어, 한 번에 최대 5개의 페이지 번호를 표시한다고 가정하면,
- 현재 페이지 그룹의 시작 페이지를 계산한다. 현재 페이지가 1~5이면 시작 페이지는 1, 6~10이면 시작 페이지는 6이 된다.
startPage = ((currentPage - 1) / 5) * 5 + 1;
endPage = Math.min(startPage + 4, totalPages);
```
3. **이전 페이지 / 다음 페이지 존재 여부 계산**:
hasPreviousPage = currentPage > 1;
hasNextPage = currentPage < totalPages;
4. **현재 페이지의 데이터 리스트 추출**:
- 데이터베이스 쿼리나 데이터 소스에서, 현재 페이지에 해당하는 데이터 리스트를 `pageSize`와 `currentPage`를 기반으로 계산하여 추출
- 예를 들어, JPA에서는 `PageRequest`를 사용하여 간단히 처리가능
PageRequest pageRequest = PageRequest.of(currentPage - 1, pageSize);
Page<T> page = repository.findAll(pageRequest);
dataList = page.getContent();
8. 실습코드
public class PageDTO<T> {
private int currentPage;
private int pageSize;
private long totalElements;
private int totalPages;
private List<T> dataList;
private int startPage;
private int endPage;
private boolean hasPreviousPage;
private boolean hasNextPage;
}
@Service
public class PostService {
@Autowired
private PostRepository postRepository;
public PageDTO<Post> getPostsPage(int currentPage, int pageSize) {
long totalElements = postRepository.count(); // 전체 게시글 수
int totalPages = (int) Math.ceil((double) totalElements / pageSize); // 전체 페이지 수
// 데이터 리스트 조회
PageRequest pageRequest = PageRequest.of(currentPage - 1, pageSize);
Page<Post> page = postRepository.findAll(pageRequest);
List<Post> dataList = page.getContent();
// PageDTO 초기화
PageDTO<Post> pageDTO = new PageDTO<>();
pageDTO.setCurrentPage(currentPage);
pageDTO.setPageSize(pageSize);
pageDTO.setTotalElements(totalElements);
pageDTO.setTotalPages(totalPages);
pageDTO.setDataList(dataList);
// startPage, endPage, hasPreviousPage, hasNextPage 계산 로직은 구현에 따라 달라질 수 있음
return pageDTO;
}
}
@RestController
@RequestMapping("/posts")
public class PostController {
@Autowired
private PostService postService;
@GetMapping
public ResponseEntity<PageDTO<Post>> getPosts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
PageDTO<Post> pageDTO = postService.getPostsPage(page, size);
return ResponseEntity.ok(pageDTO);
}
}
'TIL' 카테고리의 다른 글
[TIL] 20240308 JPA Query dsl - QuerydslPredicateExecutor (2) | 2024.03.08 |
---|---|
[TIL] 20240306 JPA N+1 문제 (0) | 2024.03.06 |
[TIL] 20240304 JPA 1차캐시 / 2차캐시 (0) | 2024.03.04 |
[TIL] 20240226 Spring JPA Query method (1) | 2024.02.26 |
[TIL] 20240221 Junit Mock 객체 / Stubbing (0) | 2024.02.21 |