
Spring Data JPA를 이용하여 페이징처리를 해보겠습니다.
사용자가 요청했을 때 데이터베이스에 있는 수천, 수만, 수백만 줄의 데이터를 모두 조회하여 제공한다면 서버의 부하가 굉장히 클 것입니다. 이를 방지하기 위해서 대부분의 서비스에서는 데이터를 일정 길이로 잘라 그 일부분만을 사용자에게 제공하는 방식을 사용합니다. 사용자는 현재 보고 있는 데이터의 다음, 이전 구간 혹은 특정 구간의 데이터를 요청하고, 전달한 구간에 해당하는 데이터를 제공받는 기술을 페이지네이션이라고 합니다.페이지네이션은 페이징과 슬라이싱이라는 핵심요소를 포함하고 있습니다.
전체 데이터를 일정한 크기의 덩어리(페이지)로 분할하는 과정을 가리킵니다. 예를 들어, 100개의 결과가 있고, 페이지당 10개씩 보여준다면 총 10개의 페이지가 생기게 됩니다. 이는 대량의 데이터를 작은 덩어리로 나누어 사용자가 한 번에 처리하기 편하게 해줍니다.
데이터의 일부를 선택하는 것으로, 특정 페이지에 해당하는 데이터 덩어리를 추출하는 과정을 의미합니다. 예를 들어, 100개의 결과 중 3페이지에 해당하는 결과들을 가져온다거나, 특정 페이지 범위 내의 결과를 추출하는 것이 슬라이싱입니다.
페이지네이션이 적용된 예시로는 페이스북, 인스타그램 등의 소셜미디어, 혹은 네이버 카페의 게시물 목록 조회 등이 있습다. 인스타에 돋보기 아이콘을 클릭하고 계속 내리면 끝없이 내려가는 것을 볼 수 있는데 이는 슬라이싱을 이용한 사례입니다.
"content": [
{
"id": 1,
"author": "user1",
"title": "test1",
"content": "test1"
},
{
"id": 2,
"author": "user2",
"title": "test2",
"content": "test2"
},
{
"id": 3,
"author": "user3",
"title": "test3",
"content": "test3"
},
{
"id": 4,
"author": "user4",
"title": "test4",
"content": "test4"
},
{
"id": 5,
"author": "user5",
"title": "test5",
"content": "test5"
}
]
여기 5개의 데이터가 있습니다. 이 데이터들을 모두 불러오는 것이 아닌 한 페이지 당 3개의 데이터만 있는도 록 하여 첫 페이지만 불러오겠습니다.
{
"page":0,
"size":3,
"sort": "asc"
}
이렇게 json형식으로 요청을 보내면
{
"content": [
{
"id": 1,
"author": "user1",
"title": "test1",
"content": "test1"
},
{
"id": 2,
"author": "user2",
"title": "test2",
"content": "test2"
},
{
"id": 3,
"author": "user3",
"title": "test3",
"content": "test3"
}
],
"pageable": {
"pageNumber": 0,
"pageSize": 3,
"sort": {
"empty": false,
"unsorted": false,
"sorted": true
},
"offset": 0,
"paged": true,
"unpaged": false
},
"last": false,
"totalPages": 2,
"totalElements": 5,
"first": true,
"size": 3,
"number": 0,
"sort": {
"empty": false,
"unsorted": false,
"sorted": true
},
"numberOfElements": 3,
"empty": false
}
보이는 JSON 응답은 Spring Data JPA가 페이징 된 데이터를 반환할 때 기본적으로 제공하는 형태입니다. 원하지 않는 정보들도 있다면 직접 커스터마이징하는 방법도 있습니다.
다음은 코드를 살펴보겠습니다.
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
public class Post {
@Id
@GeneratedValue
private Long id;
@Column(name = "AUTHOR")
private String author;
@Column(name = "TITLE")
private String title;
@Column(name = "CONTENT")
private String content;
public static Post toEntity(PostDTO postDTO){
return Post.builder()
.author(postDTO.getAuthor())
.title(postDTO.getTitle())
.content(postDTO.getContent())
.build();
}
}
간단하게 author,title,content를 포함한 엔티티를 만들었습니다.
Pageable 인터페이스에 PageRequest메서드를 이용할 것이다.
@Data
@NoArgsConstructor
public class PostDTO {
private Long id;
private String author;
private String title;
private String content;
public PostDTO(Post post){
this.id = post.getId();
this.author = post.getAuthor();
this.title = post.getTitle();
this.content = post.getContent();
}
}
엔티티를 매핑할 DTO도 만들어주고
@Data
public class PostPagingDto {
private int page;
private int size;
private String sort;
}
page의 번호와 한 page에 포함하는 데이터의 개수와 정렬 방식을 요청하기 위한 DTO도 만들어 주었다.
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
}
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
@Transactional
public void save(PostDTO postDTO) {
Post post = Post.toEntity(postDTO);
postRepository.save(post);
}
@Transactional(readOnly = true)
public Page<PostDTO> findAllPosts(PostPagingDto postPagingDto) {
Sort sort = Sort.by(Sort.Direction.fromString(postPagingDto.getSort()), "id");
Pageable pageable = PageRequest.of(postPagingDto.getPage(), postPagingDto.getSize(), sort);
Page<Post> postPages = postRepository.findAll(pageable);
Page<PostDTO> postDTOPages = postPages.map(postPage -> new PostDTO(postPage));
return postDTOPages;
}
}
Pageable 인터페이스의 PageRequest.of 메서드를 이용하였고 엔티티를 DTO로 매핑해주었다.
@RestController
@RequestMapping("/api/post")
@RequiredArgsConstructor
public class PostController {
private final PostService postService;
@PostMapping
public void save (@RequestBody PostDTO postDTO){
postService.save(postDTO);
}
@GetMapping
public Page<PostDTO> findAll(@RequestBody PostPagingDto postPagingDto){
return postService.findAllPosts(postPagingDto);
}
}
이렇게 Spring Data Jpa를 사용하여 간단하게 페이지 네이션을 구현해 보았습니다.