지난번에 포스팅한 게시글 단건 조회에 이어서 오늘은 전체 게시글을 조회하는 WAS 단 로직을 포스팅하려 합니다. 사실 JpaRepository에서 기본적으로 제공하는 findAll() 메서드를 사용하면 단건 조회와 큰 차이점은 없지만, 글이 너무 많은 경우에 비용이 너무 많이 들어가므로 페이징 처리도 함께 구현할 예정입니다.
페이징(Paging)?
우리가 웹사이트에서 흔히 볼 수 있는 페이징은 왜 필요할까요? 페이징 처리가 없다면 스크롤을 계속해서 내려야 하는 상황도 발생할 수 있고, 무엇보다 데이터 처리에 비용이 많이 발생합니다.
페이징 처리를 안 한 상황에서 글이 만약 몇십만 개가 있다면 한 번의 로직으로 DB에서 몇십만 개의 글을 모두 가져와야 할 것입니다. 물론 DB에서 데이터 사이즈로 인해 오류를 내뱉을 것이며, DB에서 감당 가능한 데이터 사이즈여도 로딩 속도가 많이 느려질 것입니다.
이러한 비용적인 측면으로 인해 페이징 처리는 게시판의 필수 요소로 자리매김 하였습니다. 지금 우리가 사용하는 Spring Data JPA 에서는 페이징과 정렬에 아주 강력한 방법을 제공합니다.
위 그림에서 알 수 있듯이 JpaRepository의 부모 인터페이스인 PagingAndSortingRepository에서는 페이징과 sorting이라는 기능을 제공합니다.
또한 인터페이스 안에는 findAll() 메소드 안에 Pageable을 파라미터로 넘겨서 페이징을 사용할 수 있다고 언급되어 있습니다.
Controller 생성📙
그렇다면 한번 사용해볼까요? Controller 클래스는 게시글 작성과 같이 비즈니스 로직은 들어가지 않아서 간단하게 구현이 가능합니다.
@GetMapping("/posts")
public List<PostResponse> getAllPosts(@PageableDefault(size = 5) Pageable pageable){
return postService.getAllPosts(pageable);
}
GetMapping으로 url을 매핑하고 이전 포스팅에서 만든 response 전용 DTO인 PostResponse를 List로 전달 받습니다. 그리고 getAllPosts() 메서드의 파라미터 값은 위에서 언급한 Pageable을 넘겨받습니다.
Pageable의 들어갈 수 있는 값은 number(확인하고 싶은 페이지), size(페이지당 글 개수), sort(정렬 방법)을 넣을 수 있습니다. 저는 @PageableDefault를 이용하여 한 페이지당 5개의 글을 볼 수 있도록 설정하였습니다.
마지막으로 postService의 getAllPosts() 메서드를 pageable과 함께 호출합니다.
Service 생성📒
Service 클래스에서는 넘겨받은 pageable과 JpaRepository에서 기본적으로 제공해주는 findAll() 메서드를 이용하여 원하는(글 개수, 정렬 방법 등) 방식으로 게시글들을 꺼내옵니다.
public List<PostResponse> getAllPosts(Pageable pageable){
return postRepository.findAll(pageable).stream()
.map(PostResponse::new)
.collect(Collectors.toList());
}
Java8 부터는 Stream을 이용할 수 있습니다. 기존에 자바 컬렉션이나 배열의 원소를 가공할 때는 for문이나 forEach 등으로 원소를 하나씩 골라내며 가공을 하였다면, 이제는 Stream을 이용하여 람다 함수 형식으로 간결하고 깔끔하게 요소들의 처리가 가능해졌습니다.
위에서 사용한 map은 요소들을 특정 조건에 해당하는 값으로 변환해 주는 역할을 하고, collect는 요소들의 가공이 끝났다면 리턴해줄 결과를 만들어 주는 역할을 합니다.
📌한마디로 findAll() 메서드를 이용하여 게시글을 가져오는데, Controller에서 지정한 size=5로 인해서 5개의 글만 가져오게 되며, map을 사용함으로 PostResponse(DTO)로 변환이 되고 이 변환된 값들이 collect로 인하여 List 형식으로 결과물이 나오게 됩니다.
이제 Controller와 Service 테스트 코드를 구현해 볼까요?
Controller Test 생성📘
첫 번째로 Controller Test 입니다. 페이징 처리를 테스트하는 것이기 때문에, given 단계에서 많은 양의 데이터를 집어넣어야 합니다.
@Test
@DisplayName("글 1페이지 조회")
void test6() throws Exception{
//given
List<Post> requestPosts = IntStream.range(1, 31)
.mapToObj(i ->
Post.builder()
.title("빈코 제목 = " + i)
.content("빈코 내용 = " + i)
.build()
)
.collect(Collectors.toList());
postRepository.saveAll(requestPosts);
// expected
mockMvc.perform(MockMvcRequestBuilders.get("/posts?page=1&sort=id,desc")
.contentType(APPLICATION_JSON)
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.length()", Matchers.is(5)))
.andExpect(jsonPath("$[0].id").value(30))
.andExpect(jsonPath("$.[0].title").value("빈코 제목 = 30"))
.andExpect(jsonPath("$.[0].content").value("빈코 내용 = 30"))
.andDo(print());
}
given section
given 단계에서는 위에서 사용했던 stream을 이용하여 게시글을 등록해주었습니다. range는 1부터 31 미만까지 총 30번의 반복을 도는 것을 의미하고, mapToObj는 주어진 함수를 적용한 결과로 구성된 객체 값 Stream을 반환해줍니다.
즉, 게시글이라는 객체를 30번의 반복문을 돌면서 생성해주고 collect를 이용하여 List 형식으로 만들어 준 값이 requestPosts가 되는 것입니다.
이후에는 JpaRepository에서 제공하는 saveAll() 메서드를 이용하여 전체 다 저장해줍니다.
expected section
expected 단계에서는 mockMvc 패턴을 이용하여 페이징 처리하는 Controller로 매핑해줍니다. 여기서 파라미터 값으로 원하는 페이지의 위치인 1페이지와, sort 방식을 id 기준 내림차순으로 지정하였습니다.
그리고 결과값과 기대하는 값을 비교해줍니다. 첫 번째로는 200코드가 내려오는지 확인하고, 페이징 기본 사이즈가 5였기 때문에 총 길이가 5인지, 첫번째로 넘어온 데이터의 id가 30인지 등등 테스트를 실행하였습니다.
페이징의 기본 설정은 Controller에서 @PageableDefault로 위에서 선언하였고, id값이 내림차순이기 때문에 30부터 시작하는 것을 알 수 있습니다.
Service Test 생성📔
Service 테스트도 Controller 테스트와 유사하게 구현이 됩니다. 다만 한 가지 다른 점은 Controller를 거치지 않기 때문에, Controller에서 기본 사이즈 설정한 5의 값을 설정해줘야 합니다.
@Test
@DisplayName("글 1페이지 조회")
void test3() {
//given
List<Post> requestPosts = IntStream.range(1, 31)
.mapToObj(i ->
Post.builder()
.title("빈코 제목 = " + i)
.content("빈코 내용 = " + i)
.build()
)
.collect(Collectors.toList());
postRepository.saveAll(requestPosts);
Pageable pageable = PageRequest.of(0,5, Sort.by(Sort.Direction.DESC,"id"));
//when
List<PostResponse> posts = postService.getAllPosts(pageable);
//then
assertEquals(5L, posts.size());
assertEquals("빈코 제목 = 30", posts.get(0).getTitle());
assertEquals("빈코 제목 = 26", posts.get(4).getTitle());
}
give section
given 단계는 Controller 테스트와 거의 동일하지만 위에서 언급했듯이 Pageable은 직접 선언하여야 합니다. PageRequest.of를 이용하여 페이지의 number, size, sort 방식을 지정해줍니다.
when section
when 단계에서는 postService의 getAllPosts() 메서드와 pageable을 이용하여 원하는 양식에 맞춰 게시글들을 가져옵니다.
then section
then 단계에서는 게시글의 개수가 5개인지, 그리고 첫 번째 게시글과 마지막 게시글의 제목 값이 기대하는 값과 일치하는지에 대한 테스트를 작성하였습니다.
👨👩👦👦 오픈채팅방 운영
취업을 준비하는 예비 개발자분들을 위한 질문&답변할 수 있는 공간을 만들었습니다. 취업과 이직을 하기 위해서 어떤 걸 중점적으로 준비해야 하는지부터 포트폴리오&이력서 작성법 등 다양한 질문들을 받고 답변을 드립니다. 참여하셔서 다양한 정보 얻고 가시면 좋을 것 같네요😁
참여코드 : 456456
https://open.kakao.com/o/gVHZP8dg
👨💻 전자책 출간
아울러 제가 🌟비전공자에서 2년만에 보안 전문 중견기업으로 이직 한 방법들을 정리한 전자책을 출간 하게 되었습니다. 어떤 걸 공부해야 하는지, 이직을 위해서 무엇을 준비해야 하는지, 제가 받았던 기술 면접 리스트 등 다양한 목차로 구성되어 있습니다. 또한, 구매 시 1:1 채팅을 이용하여 포트폴리오 첨삭을 도와드리고 있습니다. 🐕전자책으로 얻은 모든 수익은 유기견 센터 '팅*벨 입양센터'에 후원될 예정입니다. 관심 있으신 분들은 아래 링크를 참고해주세요😁
마치며
지금까지 게시글 다중 조회 및 페이징 처리 방법을 포스팅하였습니다. JPA를 사용하기 전에는 ResponseResult라는 클래스를 만들어서 paging 결과와 해당 데이터들을 담아서 넘겨주곤 했었는데, Pageable 하나로 모든 게 해결되는 것을 보니 참 신기한 것 같네요.
다음 포스팅은 게시글 수정에 관한 내용을 포스팅할 예정입니다. 코드의 결함이 있거나 궁금한 점은 댓글로 남겨주세요😄
이전 포스팅
* [ SpringBoot JPA 게시판 CRUD(Create) ]
* [ SpringBoot JPA 게시판 CRUD(Create-TDD) ]
* [ SpringBoot JPA 게시판 CRUD(Read 단건 조회 및 TDD) ]
다음 포스팅
* [ SpringBoot JPA 게시판 CRUD(Update-TDD) ]
* [ SpringBoot JPA 게시판 CRUD(Delete-TDD) ]
* [ SpringBoot JPA 게시판 CRUD(예외처리) ]
'JPA' 카테고리의 다른 글
SpringBoot JPA 게시판 CRUD 구현(Delete-TDD) (0) | 2022.07.25 |
---|---|
SpringBoot JPA 게시판 CRUD 구현(Update-게시글 수정) (0) | 2022.07.21 |
SpringBoot JPA 게시판 CRUD 구현(Read 단건 조회 및 TDD) (0) | 2022.07.20 |
SpringBoot JPA 게시판 CRUD 구현(Create - TDD) (0) | 2022.07.18 |
SpringBoot JPA 게시판 CRUD 구현(Create) (0) | 2022.07.18 |