-
JPA Paging 적용하기프로젝트/미디어 스트리밍 서버 프로젝트 2022. 8. 20. 19:25728x90
페이징이란?
만약 게시글이 100만 건이라면 어떻게 될까요?
한 화면에서 모든 게시글을 보여줄 수 없기 때문에 페이지를 나눠서 게시글을 보여주는 방식으로 해결합니다.
그러면 100만건의 10만 개의 페이지로 나누어 10건씩 데이터를 전송할 수 있습니다.
JPA와 페이징
JPA를 사용하면 PagingAndSortingRepository 인터페이스를 통해 쉽게 페이징을 구현할 수 있습니다.
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> { Page<T> findAll(Pageable pageable); }
이미 findAll이 구현되어 있고 Pageable이라는 객체를 받아 Page<T>를 반환합니다.
이때 Controller에서 받을때 Pageable객체를 그대로 받아서 전달할 수 있습니다.
@GetMapping fun getVideos(pageable: Pageable): ResponseEntity<BasicResponse<PageResponse>> { val pageResponse = videoService.findVideos(pageable) return ResponseEntity( BasicResponse(SUCCESS, pageResponse), HttpStatus.OK, ) }
Service
@Transactional(readOnly = true) fun findVideos(pageable: Pageable): PageResponse { val result = videoRepository.findAll(pageable) return PageResponse.toPageResponse(result) }
이때 Pageable의 내부의 인자는 3가지로 이루어져 있습니다.
- page(현재 몇번째 페이지를 보여줄 것인가? 0부터 시작)
- size(각 페이지에 몇 개의 데이터를 보여줄 것인가?)
- sort(정렬은 어떤식으로 이루어지는 것인가?)
HTTP 요청 방식
/video?page= &size= &sort= ,정렬방식
예시 : /video?page=0&size=3
이때 만약 아무런 인자도 넘기지 않는다면 기본값으로 0페이지의 20개의 데이터를 정렬되지 않은 정보로 제공합니다.
이때 기본값을 지정하기 위해서는 @PageableDefault를 사용할 수 있습니다.
@GetMapping("/list") @ResponseBody public Page<Book> GoBookList(@PageableDefault(size = 3) Pageable pageable){ Page<Book> books = bookDBService.FindBooksBypageRequest(pageable); return books; }
사이즈의 크기를 3개로 기본값을 제공하는 예시입니다.
이뿐만 아니라 page, sort도 지정해줄 수 있습니다.
Page 객체가 가지고 있는 값
반환 값이 Page<T>인데 Page객체는 어떤 값으로 구성되어 있을까요?
"content": [ { "videoNo": 1, "subject": "mysubject", "imageUrl": "throw IllegalStateException()" }, { "videoNo": 2, "subject": "mysubject", "imageUrl": "throw IllegalStateException()" }, { "videoNo": 3, "subject": "mysubject", "imageUrl": "throw IllegalStateException()" } ]
content 내부에는 DB의 값들이 저장되어 있습니다.
사실 여기서 DB에는 추가적인 Column들이 존재하지만 Entity를 그대로 반환하는 것은 좋지 않기 때문에 따로 DTO로 파싱 하였습니다. (밑에서 어떻게 파싱 하는지 설명하겠습니다)
현재는 size=3으로 지정하여 3개의 데이터가 불러와진 모습입니다.
Pageable에 대한 정보들이 존재합니다.
- last : 마지막 페이지인지
- totalElements : DB의 전체 데이터 개수
- totlaPages : 만들 수 있는 page 수
- size : 페이지당 나타낼 수 있는 데이터 개수
- number : 현재 페이지 번호
- sort : 정렬 정보
- first : 첫 번째 페이지 인지
- numberOfElements : 실제 데이터 개수
- empty : 리스트가 비어있는지 여부
페이징 결과 DTO로 파싱 하기
두 가지 이유로 페이징 결과를 DTO로 파싱 하려고 합니다.
1. Entity를 그대로 보여주는 것은 좋지 않다(Presentation Layer와 DB Layer를 분리시키자)
2. Pageable에서 필요한 값들만 뽑아서 보여주고 싶다(number, size, totalElements만 있으면 클라이언트는 페이징을 사용할 수 있습니다.)
PageResponse
data class PageResponse( val contents: List<VideoInfo>, val totalPage: Int, val totalElements: Long, val curPage: Int, val size: Int, ) { companion object { fun toPageResponse(page: Page<Video>): PageResponse { val videoInfoContent = page.content.map(VideoInfo::fromEntity) return PageResponse( videoInfoContent, page.totalPages, page.totalElements, page.number, page.size, ) } } } data class VideoInfo( val videoNo: Long, val subject: String, val imageUrl: String, ) { /* 아직 영상으로부터 썸네일인 imageUrl을 파싱하지 못했습니다. 추후 구현예정이며 우선 엘비스연산자를 통해 임시값을 채워넣었습니다. */ companion object { fun fromEntity(video: Video): VideoInfo { return VideoInfo( video.id!!, video.subject, video.imageUrl ?: "throw IllegalStateException()", ) } } }
compaion object를 활용하여 toPageResponse 메서드에서는 Entity를 VideoInfo로 매핑하여 Page값에서 필요한 정보들만 뽑아서 PageResponse로 반환합니다.
Companion object에 대한 간략한 소개
코틀린은 object 키워드를 사용하여 언어 수준에서 싱글톤을 지원합니다. (클래스를 정의하면서 객체를 생성하는 키워드)
이때 companion object는 static이 아니며 사용자 입장에서 static으로 동작하는 것처럼만 보입니다.
static 하게 동작하기만 하며 사실은 객체입니다.
json 결과
요청 예시
localhost:8080/video?size=3&page=0
응답 예시
"contents": [ { "videoNo": 1, "subject": "mysubject", "imageUrl": "throw IllegalStateException()" }, { "videoNo": 2, "subject": "mysubject", "imageUrl": "throw IllegalStateException()" }, { "videoNo": 3, "subject": "mysubject", "imageUrl": "throw IllegalStateException()" } ], "totalPage": 2, "totalElements": 5, "curPage": 0, "size": 3
만약 Entity가 N:1 관계에서 페이징을 할 때 N+1문제가 발생한다면 다음 글을 참고하세요
https://tecoble.techcourse.co.kr/post/2021-07-26-jpa-pageable/
출처
https://tmdrl5779.tistory.com/61
https://www.bsidesoft.com/8187
https://pearlluck.tistory.com/722
'프로젝트 > 미디어 스트리밍 서버 프로젝트' 카테고리의 다른 글
Kotest와 MockK를 활용하여 컨트롤러 단위테스트하기 (0) 2022.08.23 ffmpeg install (Mac OS, Amazon Linux) (0) 2022.08.22 Json with MultipartFile (0) 2022.08.19 [Spring + Kotlin]Kotest와 MockK를 활용한 테스트 코드 작성 (0) 2022.08.19 ktlint와 Github Action을 활용한 CI (0) 2022.08.17