Pageable
Pageable 인터페이스 분석하기
Spring을 사용하면서 웬만한 데이터들은 대부분 페이징 처리를해서 뿌려 주고 있습니다.
이번에는 Pageable을 사용하면서 가장 헷갈리는(아마 저만..?) offset, pageNumber의 차이 등을 정리해보려고 합니다.
Pageable
설명
-
페이지네이션을 위한 인터페이스들을 정의하고 있습니다.
public interface Pageable {
/** * Returns a {@link Pageable} instance representing no pagination setup. * * @return *//** * Returns whether the current {@link Pageable} contains pagination information. * * @return *///기본 인스턴스를 리턴합니다. static Pageable unpaged() { return Unpaged.INSTANCE; }
/** * Returns whether the current {@link Pageable} does not contain pagination information. * * @return *///현재 페이지네이션 정보가 있는지(?)를 알려줍니다. default boolean isPaged() { return true; }
/** * Returns the page to be returned. * * @return the page to be returned. *///현재 페이지네이션 정보가 없으면 true default boolean isUnpaged() { return !isPaged(); }
/** * Returns the number of items to be returned. * * @return the number of items of that page *///PageNumber를 리턴합니다. Spring의 페이지 인덱스는 0부터 시작합니다. int getPageNumber();
/** * Returns the offset to be taken according to the underlying page and page size. * * @return the offset to be taken *///현재 페이지의 아이템 수량을 리턴합니다. int getPageSize();
/** * Returns the sorting parameters. * * @return */// offeset을 리턴합니다 long getOffset();
/** * Returns the current {@link Sort} or the given one if the current one is unsorted. * * @param sort must not be {@literal null}. * @return */ default Sort getSortOr(Sort sort) { Assert.notNull(sort, “Fallback Sort must not be null!”); return getSort().isSorted() ? getSort() : sort; } /** * Returns the {@link Pageable} requesting the next {@link Page}. * * @return *///sort 정보를 리턴합니다. Sort getSort();
/** * Returns the previous {@link Pageable} or the first {@link Pageable} if the current one already is the first one. * * @return *///다음 페이지 정보를 리턴합니다. Pageable next();
/** * Returns the {@link Pageable} requesting the first page. * * @return *///이전 페이지 정보를 리턴합니다. 만약 지금은 첫번째 페이지라면 첫번재 페이지를 리턴합니다. Pageable previousOrFirst();
/** * Returns whether there’s a previous {@link Pageable} we can access from the current one. Will return * {@literal false} in case the current {@link Pageable} already refers to the first page. * * @return *///첫번째 페이지를 리턴합니다. Pageable first();
/** * Returns an {@link Optional} so that it can easily be mapped on. * * @return */ default Optional//이전 페이지 정보가 있는지 확인합니다. boolean hasPrevious();
toOptional() { return isUnpaged() ? Optional.empty() : Optional.of(this); } }
구현체 살펴보기!!(AbstractPageRequest)
public abstract class AbstractPageRequest implements Pageable, Serializable {
private static final long serialVersionUID = 1232825578694716871L;
private final int page;
private final int size;
/**
* Creates a new {@link AbstractPageRequest}. Pages are zero indexed, thus providing 0 for {@code page} will return
* the first page.
*
* @param page must not be less than zero.
* @param size must not be less than one.
*/
//page가 0보다 작거나, 사이즈가 1보다 작으면 에러가 발생
public AbstractPageRequest(int page, int size) {
if (page < 0) {
throw new IllegalArgumentException("Page index must not be less than zero!");
}
if (size < 1) {
throw new IllegalArgumentException("Page size must not be less than one!");
}
this.page = page;
this.size = size;
}
/*
* (non-Javadoc)
* @see org.springframework.data.domain.Pageable#getPageSize()
*/
//페이지 사이즈를 리턴 -> 여기서 말하는 페이지 사이즈는 한 페이지에 들어갈 사이즈를 이야기하는 듯
public int getPageSize() {
return size;
}
/*
* (non-Javadoc)
* @see org.springframework.data.domain.Pageable#getPageNumber()
*/
//현재 페이지 번호를 리턴합니다.
public int getPageNumber() {
return page;
}
/*
* (non-Javadoc)
* @see org.springframework.data.domain.Pageable#getOffset()
*/
//offset은 현재 페이지 * 사이즈
public long getOffset() {
return (long) page * (long) size;
}
/*
* (non-Javadoc)
* @see org.springframework.data.domain.Pageable#hasPrevious()
*/
public boolean hasPrevious() {
return page > 0;
}
/*
* (non-Javadoc)
* @see org.springframework.data.domain.Pageable#previousOrFirst()
*/
public Pageable previousOrFirst() {
return hasPrevious() ? previous() : first();
}
/*
* (non-Javadoc)
* @see org.springframework.data.domain.Pageable#next()
*/
public abstract Pageable next();
/**
* Returns the {@link Pageable} requesting the previous {@link Page}.
*
* @return
*/
public abstract Pageable previous();
/*
* (non-Javadoc)
* @see org.springframework.data.domain.Pageable#first()
*/
public abstract Pageable first();
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + page;
result = prime * result + size;
return result;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
AbstractPageRequest other = (AbstractPageRequest) obj;
return this.page == other.page && this.size == other.size;
}
}
위를 이용해서 Page객체를 만들고 테스트 해보기
public class PageTest {
private List<Integer> datas = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21);
@Test
void 두번째_페이지를_만들어보세요() {
//5개씩 나눴을 때 3번째 페이지를 불러온다.
Pageable pageRequest = PageRequest.of(2, 5);
int startIndex = (int) pageRequest.getOffset();
int endIndex = startIndex + 5;
List<Integer> pageData = datas.subList(startIndex, endIndex);
assertThat(pageData.get(0)).isEqualTo(11);
assertThat(pageData.size()).isEqualTo(5);
}
@Test
void 마지막_페이지를_만들어보세요() {
Pageable pageable = PageRequest.of(4, 5);
int dataLastIndex = datas.size();
int startIndex = Math.min((int) pageable.getOffset(), dataLastIndex);
int endIndex = Math.min(startIndex + 5, dataLastIndex);
List<Integer> pageData = datas.subList(startIndex, endIndex);
assertThat(pageData.size()).isEqualTo(1);
assertThat(pageData.get(0)).isEqualTo(21);
}
}