Pagination

최종윤·2022년 12월 18일

JPA

목록 보기
2/15
post-thumbnail

https://www.petrikainulainen.net/programming/spring-framework/spring-data-jpa-tutorial-part-seven-pagination/

우리는 contains, IgnoreCase, sorting 기능을 구현하는 정적 db query를 생성하는 법 3가지와 동적 쿼리를 생성하는 법을 배웠습니다.
하지만 이 query는 db에 존재하는 모든 entries를 return하는데 이는 performance 문제입니다.

DB 쿼리의 쿼리 결과 페이지화 하기

다음 단계를 수행하여 데이터베이스 쿼리의 쿼리 결과를 페이지화할 수 있습니다:

  1. 요청한 페이지의 정보를 지정하는 Pageable(페이지 가능 개체)객체를 가져옵니다.
  2. 페이지 가능 개체를 메소드 매개 변수로 올바른 리포지토리 메소드로 전달합니다.

Pageable 객체를 얻는 방법부터 알아보겠습니다.

Pageable 객체 가져오기

다음 두 가지 방법을 사용하여 Pageable 개체를 얻을 수 있습니다:

  1. 수동으로 만들 수 있습니다.
  2. Spring Data 웹 지원을 사용할 수 있습니다.

페이지 가능한 개체 수동 생성

수동으로 Pageable 개체를 만들려면 Spring Data JPA 저장소에서 반환되는 쿼리 결과를 페이징하려는 서비스 클래스(또는 다른 구성 요소)가 Pageable 개체를 생성하여 호출된 저장소 메서드로 전달해야 합니다.

리포지토리의 소스 코드이 메서드를 사용하는 작업관리 검색 서비스 클래스는 다음과 같습니다:

``
@Service
final class RepositoryTodoSearchService implements TodoSearchService {

private final TodoRepository repository;

@Autowired
public RepositoryTodoSearchService(TodoRepository repository) {
    this.repository = repository;
}

@Transactional(readOnly = true)
@Override
public Page<TodoDTO> findBySearchTerm(String searchTerm) {
    Pageable pageRequest = createPageRequest()
      
    //Obtain search results by invoking the preferred repository method.
    Page<Todo> searchResultPage = ...
      
    return TodoMapper.mapEntityPageIntoDTOPage(pageRequest, searchResultPage);
}
  
private Pageable createPageRequest() {
    //Create a new Pageable object here.
}

}
``

The following examples demonstrate how we can implement the private createPageRequest() method:

Example 1:
1번째 페이지의 size가 10인 정보를 담은 Pageable 객체를 생성하려면
private Pageable createPageRequest() { return new PageRequest(0, 10); }

Example 2:
2번째 페이지의 size10이고, title과 descirption에 대해 오름차순으로 정렬한 page정보를 갖는 Pageable 객체를 얻고싶다면
private Pageable createPageRequest() { return new PageRequest(1, 10, Sort.Direction.ASC, "title", "description"); }

Example 3:
2번째 페이지를 10사이즈로 title에대해 내림차순, description에 대해 오름차순으로 정렬한 정보를 가지고있는 Pageable객체를 얻으려면
private Pageable createPageRequest() { return new PageRequest(1, 10, new Sort(Sort.Direction.DESC, "description") .and(new Sort(Sort.Direction.ASC, "title")); ); }

Using Spring Data Web Support

We can enable Spring Data web support by annotating our application context configuration class with the @EnableSpringDataWebSupport annotation. The relevant part of the PersistenceContext class, which configures the persistence layer of our example application, looks as follows:
``

@Configuration
@EnableJpaAuditing(dateTimeProviderRef = "dateTimeProvider")
@EnableJpaRepositories(basePackages = {
"net.petrikainulainen.springdata.jpa.todo"
})
@EnableTransactionManagement
@EnableSpringDataWebSupport
class PersistenceContext {
}
``

This registers two HandlerMethodArgumentResolver objects that are described in the following:

The SortHandlerMethodArgumentResolver can extract sorting information from the request or from the @SortDefault annotation.

The PageableHandlerMethodArgumentResolver extracts the information of the requested page from the request.

We can now specify the information of the requested page and configure the sorting options of the invoked database query by setting the values of the following request parameters:

The page request parameter specifies the page number of the requested page. The number of the first page is 0 and the default value of this request parameter is 0 as well.

The size request parameter specifies the size of the requested page. The default value of this request parameter is 20.

The sort request parameter specifies the sorting options of the invoked query. The reference documentation of Spring Data JPA describes the content of this request parameter as follows: "Properties that should be sorted by in the format property,property(,ASC|DESC). Default sort direction is ascending. Use multiple sort parameters if you want to switch directions, e.g. ?sort=firstname&sort=lastname,asc."

After we have enabled Spring Data web support, we can inject Pageable objects into controller handler methods. The source code of the TodoSearchController class, which utilizes Spring Data web support, looks as follows:

``
@RestController
final class TodoSearchController {

private final TodoSearchService searchService;

@Autowired
public TodoSearchController(TodoSearchService searchService) {
    this.searchService = searchService;
}

@RequestMapping(value = "/api/todo/search", method = RequestMethod.GET)
public Page<TodoDTO> findBySearchTerm(@RequestParam("searchTerm") String searchTerm, 
                                      Pageable pageRequest) {
    return searchService.findBySearchTerm(searchTerm, pageRequest);
}

}
``

Pageable로 query 결과 페이지화하기

Pageable을 얻은 다음, Pageable객체를 사용해서 query results를 페이지화하는 db query를 생성해야합니다.

모든 entities를 페이지화하기

Repository interface가 CrudRepository를 상속받았다면, PagingAndSorting을 상속받도록 수정합니다.

The Page<T.> findAll(Pageable pageRequest) method returns a page of entities that fulfill the restrictions specified by the Pageable object.

db query results를 페이지화하기 위해서는 The Page<T.> findAll(Pageable pageRequest) method를 사용하면 된다.

둘째로, Repository interface를 상속받았다면 Page<T.> findAll(Pageable pageRequest) method 를 선언할 수 있다.

한 특정 페이지를 Page<T.> findAll(Pageable pageable) method로 얻을 수 있습니다.

Page

Pageble을 파라미터로하여 가져온 결과물은 Page<SomeObject.> 형태로 반환 되며, Page를 사용한다면 대부분 다수의 row를 가져오기 때문에 Page<List<SomeObject.>>의 형태로 반환을 한다. 이 페이지 객체에는 Pagination을 구현할 때 사용하면 좋은 메서드가 있으며 이는 다음과 같다.

Page에 페이지 구현에 필요한 데이터가 있다. 화면을 생각해보면 다음 데이터들이 필요하다.
이 데이터들을 얻으려면 최소 2번의 API 요청(데이터 요청, 데이터 카운트 콜)을 통해 데이터를 가져와야 한다. 2번의 요청이 싫다면, 한번의 요청으로 모든 데이터를 가져와야 하는데 이 경우에는 데이터가 매우 많을 경우에 성능 이슈가 생길 수 있다.

getTotalElements()

쿼리 결과물의 전체 데이터 개수이다. 즉, Pageable에 의해 limit키워드가 조건으로 들어가지 않는 쿼리 결과의 수 인데, 주의해야 할 점은 쿼리 결과의 갯수만 가져오지 전체 데이터를 가져오지 않는다는 점이다.
이 메서드는 게시판 기능 사용자에게 전체 데이터 개수를 알려주는 등에 사용하기 좋다.

getTotalPages()

쿼리를 통해 가져온 요소들을 size크기에 맞춰 페이징하였을 때 나오는 총 페이지의 갯수이다.
이를 활용해 쉽게 페이지 버튼의 생성이 가능하다.

getSize()

쿼리를 수행한 전체 데이터에 대해 일정 수 만큼 나눠 페이지를 구성하는데, 이 일정 수의 크기이다.

getNumber()

요소를 가져온 페이지의 번호를 의미한다.

getNumberOfElements()

페이지에 존재하는 요소의 개수이다. 최대 size의 수 만큼 나올 수 있다.

Method Name Strategy로 생성한 query results 페이지화하기

method name으로 query 생성시 페이지화 하는 단계는 다음과 같습니다.

  1. Remove the sorting logic from the method name.

  2. Add a new method parameter (Pageable object) to the query method.

  3. Decide the returned type. We can return List<T.>, Slice<T.>, or Page<T.> objects.

case 구별하지 않고, term을 포함하는 entries를 return하는 코드는 다음과 같습니다.

``
interface TodoRepository extends Repository<Todo, Long> {

List<Todo> findByDescriptionContainsOrTitleContainsAllIgnoreCase(String descriptionPart,
                                                                 String titlePart,
                                                                 Pageable pageRequest);

Page<Todo> findByDescriptionContainsOrTitleContainsAllIgnoreCase(String descriptionPart,
                                                                 String titlePart,
                                                                 Pageable pageReguest);
 
Slice<Todo> findByDescriptionContainsOrTitleContainsAllIgnoreCase(String descriptionPart,
                                                                  String titlePart,
                                                                  Pageable pageRequest);

}
``

Named Queries로 생성한 queries 결과 페이지화하기

다음 단계로 페이지화 할 수 있습니다.
1. JPQL query에 정렬 논리를 넣습니다.
2. query method의 method parameter로 Pageable을 추가합니다.
3. List, Slice, Page중 어떤 것을 return할지 결정합니다.

``

interface TodoRepository extends Repository<Todo, Long> {

List<Todo> findBySearchTermNamed(@Param("searchTerm") String searchTerm, 
                                 Pageable pageRequest);

Page<Todo> findBySearchTermNamed(@Param("searchTerm") String searchTerm, 
                                 Pageable pageRequest);
 
Slice<Todo> findBySearchTermNamed(@Param("searchTerm") String searchTerm, 
                                  Pageable pageRequest);

}
``

@Query로 생성한 results를 페이지화하기

다음 단계로 페이지화합니다.
1. JPQL query에서 정렬 논리를 삭제합니다.
2. Pageable을 parameter로 추가합니다.
3. List, Slice, Page중 어떤 객체를 return할지 결정합니다.

``

interface TodoRepository extends Repository<Todo, Long> {

@Query("SELECT t FROM Todo t WHERE " +
        "LOWER(t.title) LIKE LOWER(CONCAT('%',:searchTerm, '%')) OR " +
        "LOWER(t.description) LIKE LOWER(CONCAT('%',:searchTerm, '%'))")
List<Todo> findBySearchTerm(@Param("searchTerm") String searchTerm, 
                            Pageable pageRequest);

@Query("SELECT t FROM Todo t WHERE " +
        "LOWER(t.title) LIKE LOWER(CONCAT('%',:searchTerm, '%')) OR " +
        "LOWER(t.description) LIKE LOWER(CONCAT('%',:searchTerm, '%'))")
Page<Todo> findBySearchTerm(@Param("searchTerm") String searchTerm, 
                            Pageable pageRequest);
                             
@Query("SELECT t FROM Todo t WHERE " +
        "LOWER(t.title) LIKE LOWER(CONCAT('%',:searchTerm, '%')) OR " +
        "LOWER(t.description) LIKE LOWER(CONCAT('%',:searchTerm, '%'))")
Slice<Todo> findBySearchTerm(@Param("searchTerm") String searchTerm, 
                             Pageable pageRequest);

}
``

  • @Query에서 native-queries로 생성한 results는 페이지화할 수 없습니다.
    SQL queries를 다룰 방법이 없기 때문입니다.

JPA criteria로 queries 생성한 경우 페이지화하기

Queryldsl로 생성한 results 페이지화하기

The Page<T.> findAll(Predicate predicate, Pageable pageRequest) method returns a page of entities that match the Predicate object and fulfill the restrictions specified by the Pageable object.

Pageable을 parameter로 한 method가 Pageable의 restrictions과 Predicate를 만족하는 entities의 Page를 반환하기 때문에
List<T.> findAll(Predicate predicate) method.대신에
Page<T.> findAll(Predicate predicate, Pageable pageRequest) method 를 사용하면 페이지화할 수 있습니다.

``
@Service
final class RepositoryTodoSearchService implements TodoSearchService {

private final TodoRepository repository;

@Autowired
public RepositoryTodoSearchService(TodoRepository repository) {
    this.repository = repository;
}

@Transactional(readOnly = true)
@Override
public Page<TodoDTO> findBySearchTerm(String searchTerm, Pageable pageRequest) {
    Predicate searchPred = titleOrDescriptionContainsIgnoreCase(searchTerm);
    Page<Todo> searchResultPage = repository.findAll(searchPred, pageRequest);
    return TodoMapper.mapEntityPageIntoDTOPage(pageRequest, searchResultPage);
}

``

Pageable 얻는법

PageRequest 객체를 수동으로 생성하거나 @EnableSpringDataWebSupport 을 configuration class에 추가

native-query vs JPQL

native-SQL을 사용해 생성한 query results는 페이지화할 수 없습니다.다룰수 있는 신뢰성있는 방법이 없기 때문입니다.

정적 쿼리 페이지화

@Query와 Method Name Strategy로 생성한 query results를 페이지화하려면 JPQL에 정렬 논리를 넣고, Pageable을 parameter로 추가합니다. return type을 결정합니다.

named-query로 생성한 방식에서는 JPQL에 정렬 논리를 삭제해야합니다.
Pageable을 parameter로 추가합니다. return type을 결정합니다.

동적쿼리 페이지화

Querydsl, criteria는 Pageable을 parameter로 추가합니다. return type을 추가합니다.

profile
https://github.com/jyzayu

0개의 댓글