QueryDsl 을 사용한 페이징

이전 게시글처럼 페이징을 구현하는것도 좋은 방법이지만 QueryDsl 을 사용해서 좀 더 유연한 페이징을 한다면

내가 원하는 페이징 클래스를 따로 만들고 그 안에서 검증을 마친 데이터를 이용할 수 있다.

QueryDsl 을 적용하는 방법은 여기를 참고하자.

 

PostController

컨트롤러에서 getList() 메서드가 쿼리 파라미터를 통해 PostSearch 를 전달인자로 받는다

테스트에서 헷갈리지 않게 요청 URL 을 잘 기억하자.

@GetMapping("/posts2")
public List<PostResponse> getListQueryDsl(@ModelAttribute PostSearch postSearch) {
    return postService.getListQueryDsl(postSearch);
}

ㄴ @ModelAttribute

왜 여기에 이걸 다는지는 복습도 할 겸 여기를 참고하자.

기본값이 @ModelAttribute 이므로 아래처럼 생략해도 동작하지만 가독성을 위해 붙이는것도 좋다.

@GetMapping("/posts2")
public List<PostResponse> getListQueryDsl(PostSearch postSearch) {
    return postService.getListQueryDsl(postSearch);
}

 

PostSearch

페이징에 관련된 정보를 담는 객체이다. 

이 객체에서 기본값을 설정을 할 수 있고 서비스 정책에 맞도록  데이터를 가공할 수도 있다.

대부분의 경우 서버 정책대로 기본값을 주기 때문에 여기서 page 및 size 의 기본값을 설정한다.

또한 페이징은 인덱스 0부터 시작하기 때문에 여기서 page 의 값을 조정하고 음수가 될 수 없게 처리한다. 

@Getter
@Builder
public class PostSearch {

    private static final int MAX_SIZE = 2000;
    private static final int MIN_PAGE = 1;

    @Builder.Default
    private Integer page = 1;

    @Builder.Default
    private Integer size = 10;

    public long getOffset() {
        return (long) (Math.max(MIN_PAGE, page) - 1) * MAX_SIZE;
    }
}

ㄴ @Builder.Default

위처럼 클래스 단위로 @Builder 를 달고 따로 생성자를 만들지 않으면 클라이언트 요청에서 해당 값이 없을 시 기본값을 입력한다.

여기서는 page 의 기본값은 1이고 size 는 10으로 하겠다.

 

PostService

컨트롤러에서 받은 PostSearch 객체의 데이터로 postRepository 를 호출하여 페이징 된 Post 를 컨트롤러로 반환한다.

public List<PostResponse> getListQueryDsl(PostSearch postSearch) {
    return postRepository.getList(postSearch).stream()
        .map(post -> new PostResponse(post))
        .collect(Collectors.toList());
}

 

PostRepositoryImpl

QueryDsl 을 적용하여 원하는 페이징 메서드를 작성한다.

여기서는 PostSearch 에 담긴 데이터를 이용해 size, offset 을 설정하고 Post 의 id 값을 기준으로 내림차순 정렬한다.

@Repository
@RequiredArgsConstructor
public class PostRepositoryImpl implements PostRepositoryCustom{

    private final JPAQueryFactory jpaQueryFactory;

    @Override
    public List<Post> getList(PostSearch postSearch) {
        return jpaQueryFactory.selectFrom(QPost.post)
            .limit(postSearch.getSize())
            .offset(postSearch.getOffset())
            .orderBy(QPost.post.id.desc())
            .fetch();
    }
}

 


PostServiceTest

페이징을 확인하기 위해 Post 를 32개 집어넣고 Post 의 id 를 기준으로 10개씩 내림차순으로 페이징한다.

이 때 JSON 응답으로 Post 가 10개 내려왔는지 확인하고 첫번째와 마지막 글 제목을 확인하여

내림차순으로 원하는 결과가 나왔는지 확인하는 테스트를 작성한다.

@DisplayName("글 1페이지 내림차순 조회 QueryDsl")
    @Test
    void getListQueryDsl() throws Exception {
        // given
        Post post1 = Post.builder()
            .title("우리 이쁜 미카공주님")
            .content("우리 공주님")
            .build();
        postRepository.save(post1);

        List<Post> requestPosts = IntStream.rangeClosed(1, 30)
            .mapToObj(i -> {
                return Post.builder()
                    .title("미카 공주님 찬양 " + i)
                    .content("찬양내용 " + i)
                    .build();
            })
            .collect(Collectors.toList());
        postRepository.saveAll(requestPosts);

        Post post2 = Post.builder()
            .title("짤녀 공주면 자러감")
            .content("잘 자")
            .build();
        postRepository.save(post2);

        // expected
        mockMvc.perform(
                MockMvcRequestBuilders.get("/posts2?page=1&size=10")
                    .contentType(APPLICATION_JSON)
            )
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.length()", Matchers.is(10)))
            .andExpect(jsonPath("$[0].title", Matchers.is("짤녀 공주면 자러감")))
            .andExpect(jsonPath("$[0].content", Matchers.is("잘 자")))
            .andExpect(jsonPath("$[4].title", Matchers.is("미카 공주님 찬양 27")))
            .andExpect(jsonPath("$[4].content", Matchers.is("찬양내용 27")))
            .andDo(print());
    }
}

 

테스트 수행 시 우리가 원했던 대로 동작하며 테스트가 성공함을 알 수 있다.

 


PostControllerTest

페이징을 확인하기 위해 Post 를 32개 집어넣고 Post 의 id 를 기준으로 10개씩 내림차순으로 페이징하려 한다.

URL 에 쿼리 파라미터 형태로 넘어온 데이터가 PostSearch 에 제대로 맵핑 됐는지 확인하기 위해

JSON 응답으로 내려온 Post 갯수 및 title, content 내용을 확인하는 테스트를 작성한다.

@DisplayName("글 1페이지 내림차순 조회 QueryDsl")
@Test
void getListQueryDsl() throws Exception {
    // given
    Post post1 = Post.builder()
        .title("우리 이쁜 미카공주님")
        .content("우리 공주님")
        .build();
    postRepository.save(post1);

    List<Post> requestPosts = IntStream.rangeClosed(1, 30)
        .mapToObj(i -> {
            return Post.builder()
                .title("미카 공주님 찬양 " + i)
                .content("찬양내용 " + i)
                .build();
        })
        .collect(Collectors.toList());
    postRepository.saveAll(requestPosts);

    Post post2 = Post.builder()
        .title("짤녀 공주면 자러감")
        .content("잘 자")
        .build();
    postRepository.save(post2);

    // expected
    mockMvc.perform(
            MockMvcRequestBuilders.get("/posts2?page=1&size=10")
                .contentType(APPLICATION_JSON)
        )
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.length()", Matchers.is(10)))
        .andExpect(jsonPath("$[0].title", Matchers.is("짤녀 공주면 자러감")))
        .andExpect(jsonPath("$[0].content", Matchers.is("잘 자")))
        .andExpect(jsonPath("$[4].title", Matchers.is("미카 공주님 찬양 27")))
        .andExpect(jsonPath("$[4].content", Matchers.is("찬양내용 27")))
        .andDo(print());
}

 

테스트 수행 시 우리가 원했던 대로 동작하며 테스트가 성공함을 알 수 있다.

+ Recent posts