관리 메뉴

bright jazz music

blog08: 페이징 처리 본문

Projects/blog

blog08: 페이징 처리

bright jazz music 2023. 1. 4. 09:27

페이징 처리

 

@PageableDafault를 컨트롤러에 달아 페이징 관련 파라미터를 받아서그걸 서비스에 넘긴 뒤에 JpaRepository를 사용해서 데이터를 가져오는 작업을 했다.

 

 

 

----------

//PostController.java

package com.endofma.blog.controller;

import com.endofma.blog.domain.Post;
import com.endofma.blog.request.PostCreate;
import com.endofma.blog.response.PostResponse;
import com.endofma.blog.service.PostService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Slf4j
@RestController
@RequiredArgsConstructor
public class PostController {
    private final PostService postService;


    @PostMapping("/posts")
    public void post(@RequestBody @Valid PostCreate request) {
        postService.write(request);
//        return Map.of();
    }

    //단건 조회
    @GetMapping("/posts/{postId}")
    public PostResponse get(@PathVariable Long postId){

        PostResponse response = postService.get(postId);
        return response;
    }

    //여러 글 조회(글 목록 가져오기)
    //  /posts
    @GetMapping("/posts")

    //글이 너무 많은 경우 비용이 너무 많이 든다.
    //DB가 뻗을 수 있음.
    //DB -> 애플리케이션 서버로 전달하는 시간, 트래픽 비용이 많이 발생할 수 있다.
    //따라서 페이지 설정

//    원래는 int로 받았음
//   public List<PostResponse> getList(@RequestParam int page){

// 그러나 사용의 용이성을 위해 pageable을 사용함
//   public List<PostResponse> getList(@PageableDefault Pageable pageable){ //1로 넘겨도 0으로 보정해서 넣어줌.
    //근데 PageableDefault의 기본 size가 10이라 yml에서 설정해도 먹히지 않는다.
    //이 떄는 어노테이션을 그대로 유지하면서 size를 파라미터로 넣어주는 방법이 있다.
    //public List<PostResponse> getList(@PageableDefault(size=10) Pageable pageable){

    //또는 어노테이션을 빼고 application.yml에서 default-page-size를 설정하여 해결할 수 수있다.
   public List<PostResponse> getList(Pageable pageable){
        return postService.getList(pageable);

    }

}

 

//PostService.java

package com.endofma.blog.service;

import com.endofma.blog.domain.Post;
import com.endofma.blog.repository.PostRepository;
import com.endofma.blog.request.PostCreate;
import com.endofma.blog.response.PostResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@Service
//@RequiredArgsConstructor
public class PostService {

    private final PostRepository postRepository;
    public PostService(PostRepository postRepository){
        this.postRepository = postRepository;
    }

    public void write(PostCreate postCreate) {
        //PostCreate 일반 클래스 ==> Post 엔티티
        Post post = Post.builder()
                .title(postCreate.getTitle())
                .content(postCreate.getContent())
                .build();

        postRepository.save(post);
    }

    //단건 조회
    public PostResponse get(Long id) {

        Post post = postRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 글입니다.")); //있으면 post반환 없으면 에러 반환

        //응답 클래스를 분리
        return PostResponse.builder()
                .id(post.getId())
                .title(post.getTitle())
                .content(post.getContent())
                .build();

    }

//    public List<Post> getList() {
//        return postRepository.findAll();
//    }

    public List<PostResponse> getList(Pageable pageable){
        //application.yml 파일의 설정처럼 web요청에 1로 왔을 때 내부적으로는 0으로 변경해야 한다.
        //그러나 파라미터를 수동으로 받기 때문에 동작하지 않는다.
        //이 때는 컨트롤러에서 @RequestParam으로 페이지를 받지 않고 @Pageabledefault로 받으면 된다.

        //Pageable로 받으면 아래 코드는 필요 없어진다.
//        Pageable pageable = PageRequest.of(page, 5, Sort.by(Sort.Direction.DESC, "id")); //springframework.data.domain

        return postRepository.findAll(pageable).stream() //pageable
                .map(PostResponse::new)
                .collect(Collectors.toList());
    }
}

 

#application.yml
spring:
  h2:
    console:
      enabled: true
      path: /h2-console

#0이 아닌 1부터 시작하는 걸로 하겠다는 의미
  data:
    web:
      pageable:
        one-indexed-parameters: true
        default-page-size: 5

  datasource:
    url: jdbc:h2:mem:blog
    username: sa
    password:
    driver-class-name: org.h2.Driver

 

 

//PostServiceTest.java

package com.endofma.blog.service;


@SpringBootTest
class PostServiceTest {

    @Autowired
    private PostService postService;

    @Autowired
    private PostRepository postRepository;

    @BeforeEach
    void clean(){
        postRepository.deleteAll();
    }

//...

    @Test
    @DisplayName("글 1페이지 조회")
    void test3(){
        //given
        List<Post> requestPost = IntStream.range(1, 31) //for (int =0; i<30; i++)
                .mapToObj(i -> Post.builder()
                        .title("블로그 제목 " + i)
                        .content("제이드 " + i)
                        .build()).collect(Collectors.toList());

        postRepository.saveAll(requestPost);


        // sql -> select, limit, offset 알아야 함.

        Pageable pageable = PageRequest.of(0, 5, Sort.Direction.DESC, "id");

        //when
        List<PostResponse> posts = postService.getList(pageable);

       //then
        assertEquals(5L, posts.size());
        assertEquals("블로그 제목 30", posts.get(0).getTitle());
        assertEquals("블로그 제목 26", posts.get(4).getTitle());

    }

}

 

//PostControllerTest.java

//...

//@WebMvcTest //간단한 웹테스트만 가능. 우린 스프링 전반에 걸쳐 여러 가지를 만들었기 때문에 @SpringBootTest가 필요
@AutoConfigureMockMvc //@WebMvcTest가 없어지면 기존 테스트가 안되므로 @WebMvcTest를 구성하는 애노테이션을 떼내어 붙였다.
@SpringBootTest
class PostControllerTest {



    @Autowired
    private MockMvc mockMvc; ////Could not autowire. No beans of 'MockMvc' type found.

    @Autowired
    private PostRepository postRepository; //DB저장 테스트를 위해 주입

    @BeforeEach
    void clean() {
        postRepository.deleteAll();
    }

    //...

    @Test
    @DisplayName("글 여러 개 조회")
    void test5() throws Exception{
        //given
        List<Post> requestPost = IntStream.range(1, 31) //for (int =0; i<30; i++)
                .mapToObj(i -> Post.builder()
                        .title("블로그 제목 " + i)
                        .content("제이드 " + i)
                        .build()).collect(Collectors.toList());
        postRepository.saveAll(requestPost);


        //expected
//        mockMvc.perform(get("/posts?page=1")
        mockMvc.perform(get("/posts?page=1&sort=id,desc")
//        mockMvc.perform(get("/posts?page=1&sort=id,desc&size=5")
                //size를 매번 넣어주기 귀찮으면 application.yml에 가서 default-page-size를 설정해 준다.
                //보통 현업에서는 10개보기 ,20개 보기 등의 옵션이 없으면 그냥 서버가 주는대로 받기 때문에
                //size를 파라미터로 넘기는 경우가 많지는 않다.
                        .contentType(APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.length()", is(5)))
                .andExpect(jsonPath("$[0].id").value(30))
                .andExpect(jsonPath("$[0].title").value("블로그 제목 30"))
                .andExpect(jsonPath("$[0].content").value("제이드 30"))
                .andDo(print());

    }
}

 

결과

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.6)

2023-01-04 09:22:58.931  INFO 40156 --- [    Test worker] c.e.blog.controller.PostControllerTest   : Starting PostControllerTest using Java 11.0.12 on DESKTOP-8H1PTVG with PID 40156 (started by markany-hjcha in D:\personal\blog)
2023-01-04 09:22:58.932  INFO 40156 --- [    Test worker] c.e.blog.controller.PostControllerTest   : No active profile set, falling back to 1 default profile: "default"
2023-01-04 09:22:59.539  INFO 40156 --- [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2023-01-04 09:22:59.586  INFO 40156 --- [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 40 ms. Found 1 JPA repository interfaces.
2023-01-04 09:23:00.055  INFO 40156 --- [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-01-04 09:23:00.260  INFO 40156 --- [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2023-01-04 09:23:00.324  INFO 40156 --- [    Test worker] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2023-01-04 09:23:00.383  INFO 40156 --- [    Test worker] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.6.14.Final
2023-01-04 09:23:00.621  INFO 40156 --- [    Test worker] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2023-01-04 09:23:00.769  INFO 40156 --- [    Test worker] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2023-01-04 09:23:01.689  INFO 40156 --- [    Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2023-01-04 09:23:01.699  INFO 40156 --- [    Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-01-04 09:23:02.232  WARN 40156 --- [    Test worker] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2023-01-04 09:23:02.482  INFO 40156 --- [    Test worker] o.s.b.a.h2.H2ConsoleAutoConfiguration    : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:blog'
2023-01-04 09:23:02.748  INFO 40156 --- [    Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2023-01-04 09:23:02.748  INFO 40156 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Initializing Servlet ''
2023-01-04 09:23:02.749  INFO 40156 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Completed initialization in 1 ms
2023-01-04 09:23:02.769  INFO 40156 --- [    Test worker] c.e.blog.controller.PostControllerTest   : Started PostControllerTest in 4.163 seconds (JVM running for 6.511)

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /posts
       Parameters = {page=[1], sort=[id,desc]}
          Headers = [Content-Type:"application/json;charset=UTF-8"]
             Body = null
    Session Attrs = {}

Handler:
             Type = com.endofma.blog.controller.PostController
           Method = com.endofma.blog.controller.PostController#getList(Pageable)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = [{"id":30,"title":"블로그 제목 30","content":"제이드 30"},{"id":29,"title":"블로그 제목 29","content":"제이드 29"},{"id":28,"title":"블로그 제목 28","content":"제이드 28"},{"id":27,"title":"블로그 제목 27","content":"제이드 27"},{"id":26,"title":"블로그 제목 26","content":"제이드 26"}]
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
2023-01-04 09:23:03.263  INFO 40156 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2023-01-04 09:23:03.264  INFO 40156 --- [ionShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
2023-01-04 09:23:03.266  WARN 40156 --- [ionShutdownHook] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 90121, SQLState: 90121
2023-01-04 09:23:03.266 ERROR 40156 --- [ionShutdownHook] o.h.engine.jdbc.spi.SqlExceptionHelper   : Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-214]
2023-01-04 09:23:03.267  WARN 40156 --- [ionShutdownHook] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 90121, SQLState: 90121
2023-01-04 09:23:03.267 ERROR 40156 --- [ionShutdownHook] o.h.engine.jdbc.spi.SqlExceptionHelper   : Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-214]
2023-01-04 09:23:03.267  WARN 40156 --- [ionShutdownHook] o.s.b.f.support.DisposableBeanAdapter    : Invocation of destroy method failed on bean with name 'entityManagerFactory': org.hibernate.exception.JDBCConnectionException: Unable to release JDBC Connection used for DDL execution
2023-01-04 09:23:03.267  INFO 40156 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2023-01-04 09:23:03.271  INFO 40156 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
BUILD SUCCESSFUL in 7s
4 actionable tasks: 1 executed, 3 up-to-date
AM 9:23:03: Execution finished ':test --tests "com.endofma.blog.controller.PostControllerTest.test5"'.
Comments