spring boot/기술 적용

JPA - pageable을 이용한 paging

ballde 2021. 10. 31. 22:22

이런식으로 페이징이 되어있는 사이트가 많이 있다. 

 JPA 에서 또 Spring Data jpa 프로젝트에서는 효과적으로 페이징을 처리할 수 있게 방법을 제공한다.

 

제가 프로젝트 하면서 사용한 코드들인데 나중에도 기회가 되면 쓰려고 정리합니다 ㅎㅎ

 

 

먼저 querydsl을 사용하지 않고 spring data jpa를 사용할 때부터 보겠습니다.

spring data jpa 사용법은 https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation  여기를 봐주세요.

PageRequest.of에는 page(0부터 시작) size를 적어주면 됩니다.

Pageable pageable = PageRequest.of(request.getPage() - 1, request.getSize(), Sort.by(DESC, sort, "id"));

 

Page<Board> findByTitleContaining(String search1, Pageable pageable);

spring data jpa를 사용할 경우에는 위에 작성한 pageable을 인수로 넘겨주면 Page<>로 반환되는 함수를 자동생성해줍니다.

"content": [
      {
        "createdDate": "2021-10-31T21:58:22.330698",
        "modifiedDate": "2021-10-31T21:58:22.330698",
        "id": 1,
        "memberId": 1,
        "title": "신고요1",
      }
    ],
    "pageable": {
      "sort": {
        "sorted": true,
        "unsorted": false,
        "empty": false
      },
      "pageNumber": 0,
      "pageSize": 3,
      "offset": 0,
      "paged": true,
      "unpaged": false
    },
    "totalPages": 1,
    "totalElements": 1,
    "last": true,
    "sort": {
      "sorted": true,
      "unsorted": false,
      "empty": false
    },
    "size": 3,
    "numberOfElements": 1,
    "number": 0,
    "first": true,
    "empty": false
  }

이런식으로 나오게 됩니다.

하지만 이렇게 나오게 되면 불편하기 때문에 저는 stream을 이용해서 dto를 이용해서 반환해줍니다.

List<BoardInfo> boardList = boardRepository.findBySearchingAndCategory(search, category, pageable)
                .stream().map(board -> BoardInfo.of(board)
                .collect(Collectors.toList());

 

하지만 spring data jpa로는 한계가 있기 때문에 querydsl을 많이 사용하는데요.

querydsl 에서 pageable을 사용하는 부분을 보도록 하겠습니다.

 

    @Override
    public Page<Board> findBySearching(String search, Pageable pageable) {
        QueryResults<Board> results = queryFactory.selectFrom(board)
                .where(
                        board.title.contains(search)
                )
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .orderBy(
                        getOrderSpecifier(pageable.getSort()).toArray(OrderSpecifier[]::new)
                )
                .fetchResults();

        List<Board> content = results.getResults();
        long total = results.getTotal();
        return new PageImpl<>(content, pageable, total);
    }
    
  private List<OrderSpecifier> getOrderSpecifier(Sort sort) {
        List<OrderSpecifier> orders = new ArrayList<>();
        // Sort
        sort.stream().forEach(order -> {
            Order direction = order.isAscending() ? Order.ASC : Order.DESC;
            String prop = order.getProperty();
            PathBuilder orderByExpression = new PathBuilder(Board.class, "board");
            orders.add(new OrderSpecifier(direction, orderByExpression.get(prop)));
        });
        return orders;
    }
Pageable pageable = PageRequest.of(request.getPage() - 1, request.getSize(), Sort.by(DESC, sort, "id"));

pageable에서 offset을 적어주지는 않았지만 jpa의 Page가 알아서 처리해줍니다. 

그래서 pageable의 offset 과 pageSize를 통해서 가지고 오고 Sort부분은 getOrderSpecifier함수를 통해서 해줍니다. 

querydsl에서 orderBy는 OrderSpecifier type을 가진 것만 올 수 있습니다. 

 

그리고 fetchResults()를 통해 가지고 온 것을 통해 List<Board>와 총 개수 total 개수를 PageImpl<>() 여기에 넣어주면 됩니다. 

 

그리고 제가 이 과정에서 nativeQuery를 통해서 pageable작성한 것 또한 써보겠습니다. querydsl이 더 좋아보이지만 그 과정에서 삽질을 해서 ㅎㅎ 혹시 모르니까 기억해놓자는 차원에서 적어봅니다 .

 

@Query(value = "select b.* from board where title like CONCAT('%', ?1, '%')",
            countQuery = "select count(*) from board where title like CONCAT('%', ?1, '%') or petition_title like CONCAT('%', ?1, '%')",
            nativeQuery = true
    )
    Page<Board> findBoardPagination(String search, Pageable pageable);

사실 별거는 없고 쿼리를 잘 짠다음에 countQuery를 추가로 적어줘야합니다.