@Repository
public interface ArticleRepository extends JpaRepository<Article, Integer> {
@Query("SELECT a FROM Article a WHERE a.status = 1")
Page<Article> findAllActiveArticle(Pageable pageable);
}
반환타입을 Page<Article> 설정했음.
매개변수에 Pageable이 들어간 이유는 JPA가 SQL 쿼리에 페이징과 정렬 조건을 동적으로 추가.
Ex) MySQL일 경우 LIMIT, OFFSET, ORDER BY @Service
@RequiredArgsConstructor
public class ArticleService {
private final ArticleRepository articleRepository;
public Page<Article> getList(int page) {
Pageable pageable = PageRequest.of(page, 10);
return articleRepository.findAllActiveArticle(pageable);
}
.....
}
page를 이용해서 PageRequest 클래스의 static메서드 of를 호출함.↓ PageRequest
public class PageRequest extends AbstractPageRequest {
private final Sort sort;
protected PageRequest(int pageNumber, int pageSize, Sort sort) {
super(pageNumber, pageSize);
Assert.notNull(sort, "Sort must not be null");
this.sort = sort;
}
// 1
public static PageRequest of(int pageNumber, int pageSize) {
return of(pageNumber, pageSize, Sort.unsorted());
}
// 2
public static PageRequest of(int pageNumber, int pageSize, Sort sort) {
return new PageRequest(pageNumber, pageSize, sort);
}
....
}
PageRequest.of(page, 10)코드에서 인자(argument)가 2개이므로 1번 메서드가 호출됨.
2번 메서드를 호출해서 최종적으로 PageRequest 생성자를 통해 PageRequest 객체가 생성됨.생성자 내부에는 super(pageNumber, pageSize);가 있으니 부모클래스인 AbstractPageRequest(추상클래스)의 생성자를 호출함.
↓ AbstractPageRequest
public abstract class AbstractPageRequest implements Pageable, Serializable {
private static final long serialVersionUID = 1232825578694716871L;
private final int pageNumber;
private final int pageSize;
public AbstractPageRequest(int pageNumber, int pageSize) {
if (pageNumber < 0) {
throw new IllegalArgumentException("Page index must not be less than zero");
} else if (pageSize < 1) {
throw new IllegalArgumentException("Page size must not be less than one");
} else {
this.pageNumber = pageNumber;
this.pageSize = pageSize;
}
}
.....
}
추상클래스의 경우 객체를 생성할 수 없고 AbstractPageRequest의 멤버필드 값을 초기화하는 용도임.Pageable 인터페이스를 구현한 구현체이므로 Pageable타입의 참조변수로 참조할 수 있음.Pageable pageable = PageRequest.of(page, 10);page는 pageNumber, 즉 조회할 페이지 번호이고.10은 pageSize, 즉 한 페이지당 보여줄 개수를 뜻함.@RequestMapping("/article")
@RequiredArgsConstructor
@Controller
public class ArticleController {
private final ArticleService articleService;
/*
전체 게시판 글 조회.
@Param
page : 클라이언트에서 요청한 페이지 번호.
*/
@GetMapping("/list")
public String list(Model model, @RequestParam(value = "page", defaultValue = "0") int page) {
Page<Article> articlePage = articleService.getList(page);
model.addAttribute("articlePage", articlePage);
return "article_list";
}
....
}
?page=x형태의 쿼리스트링으로 GET 요청이 올 것이기 때문에 URL에서 page(key)에 해당하는 값(value)를 가져오기 위해서 @RequestParam에노테이션을 사용.
Page 인터페이스를 구현한 PageImpl코드를 참고하면 사용할 수 있는 필드와 메서드를 확인할 수 있음.
....
<table class="table">
<thead class="table-dark">
<tr>
<th>번호</th>
<th>제목</th>
<th>작성일시</th>
</tr>
</thead>
<tbody>
<tr th:each="article, loop : ${articlePage}">
<!-- 번호 -->
<td th:text="${loop.count}"></td>
<!-- 제목 -->
<td>
<a th:href="@{|/article/detail/${article.id}|}" th:text="${article.title}"></a>
</td>
<!-- 작성일시 (#temporals.format(날짜 객체, 날짜 포맷)) -->
<td th:text="${#temporals.format(article.createDate, 'yyyy-MM-dd HH:mm')}"></td>
</tr>
</tbody>
</table>
....

http://localhost:8080/article/list?page=0 .....
</table>
<!-- 페이징 처리. -->
<div th:if="${!articlePage.isEmpty()}">
<ul class="pagination justify-content-center">
<li class="page-item" th:classappend="${!articlePage.hasPrevious()} ? 'disabled'">
<a class="page-link" th:href="@{|?page=0|}">«</a>
</li>
<li class="page-item" th:classappend="${!articlePage.hasPrevious()} ? 'disabled'">
<a class="page-link" th:href="@{|?page=${articlePage.number - 1}|}">
<span><</span>
</a>
</li>
<li th:each="page : ${#numbers.sequence(0, articlePage.totalPages - 1)}"
th:if="${page >= articlePage.number - 5 and page <= articlePage.number + 5}"
th:classappend="${page == articlePage.number} ? 'active'" class="page-item">
<a th:text="${page + 1}" class="page-link" th:href="@{|?page=${page}|}"></a>
</li>
<li class="page-item" th:classappend="${!articlePage.hasNext()} ? 'disabled'">
<a class="page-link" th:href="@{|?page=${articlePage.number + 1}|}">
<span>></span>
</a>
</li>
<li class="page-item" th:classappend="${!articlePage.hasNext()} ? 'disabled'">
<a class="page-link" th:href="@{|?page=${articlePage.totalPages - 1}|}">»</a>
</li>
</ul>
</div>
<div class="text-end">
<a th:href="@{/article/create}" class="btn btn-primary">글쓰기</a>
</div>
</div>
</html>
articlePage.~~~에 대한 코드는 이해할 수 있음.PageImpl
public class PageImpl<T> extends Chunk<T> implements Page<T> {
private static final long serialVersionUID = 867755909294344406L;
private final long total;
....
public int getTotalPages() {
return this.getSize() == 0 ? 1 : (int)Math.ceil((double)this.total / (double)this.getSize());
}
public long getTotalElements() {
return this.total;
}
public boolean hasNext() {
return this.getNumber() + 1 < this.getTotalPages();
}
.....
}
Chunk
abstract class Chunk<T> implements Slice<T>, Serializable {
private static final long serialVersionUID = 867755909294344406L;
private final List<T> content = new ArrayList();
private final Pageable pageable;
....
public int getNumber() {
return this.pageable.isPaged() ? this.pageable.getPageNumber() : 0;
}
public int getSize() {
return this.pageable.isPaged() ? this.pageable.getPageSize() : this.content.size();
}
public int getNumberOfElements() {
return this.content.size();
}
public boolean hasPrevious() {
return this.getNumber() > 0;
}
....
}
th:each="page : ${#numbers.sequence(0, articlePage.totalPages - 1)}"
#numbers : Thymeleaf에서 제공하는 숫자 관련 유틸 객체.sequence(start, end) : start부터 end까지 연속된 숫자 배열 생성함.${#numbers.sequence(0, 4)} ====>> [0, 1, 2, 3, 4]
articlePage.totalPages의 값이 5라면, 반복 변수 page에 0, 1, 2, 3, 4가 순서대로 들어감.-1 하는 이유첫 페이지가 0이라서 두 번째 페이지 = 1,,,,마지막 페이지 = totalPages - 1이 되기 때문임. @Test
void CreateData() {
for (int i = 0; i < 100; i++) {
ArticleDto articleDto = new ArticleDto();
articleDto.setTitle(String.format("임시 데이터 : %05d" ,i));
articleDto.setContent("임시 데이터");
articleRepository.save(Article.create(articleDto));
}
}

↓ PageRequest
public class PageRequest extends AbstractPageRequest {
private final Sort sort;
protected PageRequest(int pageNumber, int pageSize, Sort sort) {
super(pageNumber, pageSize);
Assert.notNull(sort, "Sort must not be null");
this.sort = sort;
}
// 1
public static PageRequest of(int pageNumber, int pageSize) {
return of(pageNumber, pageSize, Sort.unsorted());
}
// 2
public static PageRequest of(int pageNumber, int pageSize, Sort sort) {
return new PageRequest(pageNumber, pageSize, sort);
}
....
}
pageNumber, pageSize는 super()를 통해 부모클래스에 정의된 생성자를 호출해서 필드 값을 초기화하고, sort는 PageRequest에 있는 필드 값을 초기화하는 용도.@Service
@RequiredArgsConstructor
@Slf4j
public class ArticleService {
private final ArticleRepository articleRepository;
public Page<Article> getList(int page, int size) {
List<Sort.Order> orderList = new ArrayList<>();
orderList.add(Sort.Order.desc("createDate"));
Pageable pageable = PageRequest.of(page, size, Sort.by(orderList));
Page<Article> articlePage = articleRepository.findAllActiveArticle(pageable);
if (articlePage.getContent().isEmpty()) {
log.info("size : {}, page : {}", size, page);
throw new DataNotFoundException("존재하지 않는 페이지 요청.");
}
return articlePage;
}
....
}
Order (Inner Class)에 정의 되어 있는 desc() 메서드를 이용함.public class Sort implements Streamable<Order>, Serializable {
private static final long serialVersionUID = 5737186511678863905L;
private static final Sort UNSORTED = by();
public static final Direction DEFAULT_DIRECTION;
private final List<Order> orders;
protected Sort(List<Order> orders) {
this.orders = orders;
}
private Sort(Direction direction, @Nullable List<String> properties) {
if (properties != null && !properties.isEmpty()) {
this.orders = (List)properties.stream().map((it) -> {
return new Order(direction, it);
}).collect(Collectors.toList());
} else {
throw new IllegalArgumentException("You have to provide at least one property to sort by");
}
}
.....
public static class Order implements Serializable {
private static final long serialVersionUID = 1522511010900108987L;
private static final boolean DEFAULT_IGNORE_CASE = false;
private static final NullHandling DEFAULT_NULL_HANDLING;
private final Direction direction;
private final String property;
private final boolean ignoreCase;
private final NullHandling nullHandling;
public Order(@Nullable Direction direction, String property) {
this(direction, property, false, DEFAULT_NULL_HANDLING);
}
public Order(@Nullable Direction direction, String property, NullHandling nullHandlingHint) {
this(direction, property, false, nullHandlingHint);
}
public Order(@Nullable Direction direction, String property, boolean ignoreCase, NullHandling nullHandling) {
if (!StringUtils.hasText(property)) {
throw new IllegalArgumentException("Property must not be null or empty");
} else {
this.direction = direction == null ? Sort.DEFAULT_DIRECTION : direction;
this.property = property;
this.ignoreCase = ignoreCase;
this.nullHandling = nullHandling;
}
}
public static Order by(String property) {
return new Order(Sort.DEFAULT_DIRECTION, property);
}
public static Order asc(String property) {
return new Order(Sort.Direction.ASC, property, DEFAULT_NULL_HANDLING);
}
public static Order desc(String property) {
return new Order(Sort.Direction.DESC, property, DEFAULT_NULL_HANDLING);
}
.....
}
.....
}
asc()는 오름차순 정렬이고 사용할 desc()는 내림차순임.public static Order desc(String property) {
return new Order(Sort.Direction.DESC, property, DEFAULT_NULL_HANDLING);
}
↓ 호출
public Order(@Nullable Direction direction, String property, NullHandling nullHandlingHint) {
this(direction, property, false, nullHandlingHint);
}
↓ 호출
public Order(@Nullable Direction direction, String property, boolean ignoreCase, NullHandling nullHandling) {
if (!StringUtils.hasText(property)) {
throw new IllegalArgumentException("Property must not be null or empty");
} else {
this.direction = direction == null ? Sort.DEFAULT_DIRECTION : direction;
this.property = property;
this.ignoreCase = ignoreCase;
this.nullHandling = nullHandling;
}
}
desc() 호출.new Order(Sort.Direction.DESC, property, DEFAULT_NULL_HANDLING) 생성자 호출.this(direction, property, false, nullHandlingHint); this()를 이용해서 같은 클래스의 다른 생성자를 호출. ....
<table class="table">
<thead class="table-dark">
<tr>
<th>번호</th>
<th>제목</th>
<th>작성일시</th>
</tr>
</thead>
<tbody>
<tr th:each="article, loop : ${articlePage}">
<!-- 번호 -->
<td th:text="${articlePage.getTotalElements() - (articlePage.number * articlePage.size) - loop.index}"></td>
<!-- 제목 -->
<td>
<a th:href="@{|/article/detail/${article.id}|}" th:text="${article.title}"></a>
</td>
<!-- 작성일시 (#temporals.format(날짜 객체, 날짜 포맷)) -->
<td th:text="${#temporals.format(article.createDate, 'yyyy-MM-dd HH:mm')}"></td>
</tr>
</tbody>
</table>
....
articlePage.getTotalElements() : 전체 데이터의 개수.articlePage.number : 현재 페이지 번호.articlePage.size : 페이지당 글 수.loop.index : 나열 인덱스 (0부터 시작, 페이지가 바뀔때마다 값이 초기화됨.)Ex) 현재 총데이터는 503개인데 한 페이지당 50개씩해서 4페이지를 보면?
- loop.index로 인해 숫자가 1씩 감소하는식.


상속계층도는 아래와 같음.
PageImpl 클래스
↑
Page 인터페이스,Chunk<T> 추상 클래스
Page 인터페이스
↑
Slice<T> 인터페이스
↑
Streamable<T> 인터페이스
↑
Iterable<T>, Supplier<Stream<T>> 인터페이스
package org.springframework.data.domain;
import java.util.List;
import java.util.function.Function;
import org.springframework.lang.Nullable;
public class PageImpl<T> extends Chunk<T> implements Page<T> {
private static final long serialVersionUID = 867755909294344406L;
private final long total;
public PageImpl(List<T> content, Pageable pageable, long total) {
super(content, pageable);
this.total = (Long)pageable.toOptional().filter((it) -> {
return !content.isEmpty();
}).filter((it) -> {
return it.getOffset() + (long)it.getPageSize() > total;
}).map((it) -> {
return it.getOffset() + (long)content.size();
}).orElse(total);
}
public PageImpl(List<T> content) {
this(content, Pageable.unpaged(), null == content ? 0L : (long)content.size());
}
public int getTotalPages() {
return this.getSize() == 0 ? 1 : (int)Math.ceil((double)this.total / (double)this.getSize());
}
public long getTotalElements() {
return this.total;
}
public boolean hasNext() {
return this.getNumber() + 1 < this.getTotalPages();
}
public boolean isLast() {
return !this.hasNext();
}
public <U> Page<U> map(Function<? super T, ? extends U> converter) {
return new PageImpl(this.getConvertedContent(converter), this.getPageable(), this.total);
}
public String toString() {
String contentType = "UNKNOWN";
List<T> content = this.getContent();
if (!content.isEmpty() && content.get(0) != null) {
contentType = content.get(0).getClass().getName();
}
return String.format("Page %s of %d containing %s instances", this.getNumber() + 1, this.getTotalPages(), contentType);
}
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof PageImpl)) {
return false;
} else {
PageImpl<?> that = (PageImpl)obj;
return this.total == that.total && super.equals(obj);
}
}
public int hashCode() {
int result = 17;
result += 31 * (int)(this.total ^ this.total >>> 32);
result += 31 * super.hashCode();
return result;
}
}
Chunk<T> 추상 클래스.package org.springframework.data.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.util.Assert;
abstract class Chunk<T> implements Slice<T>, Serializable {
private static final long serialVersionUID = 867755909294344406L;
private final List<T> content = new ArrayList();
private final Pageable pageable;
public Chunk(List<T> content, Pageable pageable) {
Assert.notNull(content, "Content must not be null");
Assert.notNull(pageable, "Pageable must not be null");
this.content.addAll(content);
this.pageable = pageable;
}
public int getNumber() {
return this.pageable.isPaged() ? this.pageable.getPageNumber() : 0;
}
public int getSize() {
return this.pageable.isPaged() ? this.pageable.getPageSize() : this.content.size();
}
public int getNumberOfElements() {
return this.content.size();
}
public boolean hasPrevious() {
return this.getNumber() > 0;
}
public boolean isFirst() {
return !this.hasPrevious();
}
public boolean isLast() {
return !this.hasNext();
}
public Pageable nextPageable() {
return this.hasNext() ? this.pageable.next() : Pageable.unpaged();
}
public Pageable previousPageable() {
return this.hasPrevious() ? this.pageable.previousOrFirst() : Pageable.unpaged();
}
public boolean hasContent() {
return !this.content.isEmpty();
}
public List<T> getContent() {
return Collections.unmodifiableList(this.content);
}
public Pageable getPageable() {
return this.pageable;
}
public Sort getSort() {
return this.pageable.getSort();
}
public Iterator<T> iterator() {
return this.content.iterator();
}
protected <U> List<U> getConvertedContent(Function<? super T, ? extends U> converter) {
Assert.notNull(converter, "Function must not be null");
Stream var10000 = this.stream();
Objects.requireNonNull(converter);
return (List)var10000.map(converter::apply).collect(Collectors.toList());
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof Chunk)) {
return false;
} else {
Chunk<?> that = (Chunk)obj;
boolean contentEqual = this.content.equals(that.content);
boolean pageableEqual = this.pageable.equals(that.pageable);
return contentEqual && pageableEqual;
}
}
public int hashCode() {
int result = 17;
result += 31 * this.pageable.hashCode();
result += 31 * this.content.hashCode();
return result;
}
}
상속계층도는 아래와 같음.
Page 인터페이스
↑
Slice<T> 인터페이스
↑
Streamable<T> 인터페이스
↑
Iterable<T>, Supplier<Stream<T>> 인터페이스
package org.springframework.data.domain;
import java.util.Collections;
import java.util.function.Function;
public interface Page<T> extends Slice<T> {
static <T> Page<T> empty() {
return empty(Pageable.unpaged());
}
static <T> Page<T> empty(Pageable pageable) {
return new PageImpl(Collections.emptyList(), pageable, 0L);
}
int getTotalPages();
long getTotalElements();
<U> Page<U> map(Function<? super T, ? extends U> converter);
}
Slice<T> 인터페이스package org.springframework.data.domain;
import java.util.List;
import java.util.function.Function;
import org.springframework.data.util.Streamable;
public interface Slice<T> extends Streamable<T> {
int getNumber();
int getSize();
int getNumberOfElements();
List<T> getContent();
boolean hasContent();
Sort getSort();
boolean isFirst();
boolean isLast();
boolean hasNext();
boolean hasPrevious();
default Pageable getPageable() {
return PageRequest.of(this.getNumber(), this.getSize(), this.getSort());
}
Pageable nextPageable();
Pageable previousPageable();
<U> Slice<U> map(Function<? super T, ? extends U> converter);
default Pageable nextOrLastPageable() {
return this.hasNext() ? this.nextPageable() : this.getPageable();
}
default Pageable previousOrFirstPageable() {
return this.hasPrevious() ? this.previousPageable() : this.getPageable();
}
}
Streamable<T> 인터페이스package org.springframework.data.util;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.util.Assert;
@FunctionalInterface
public interface Streamable<T> extends Iterable<T>, Supplier<Stream<T>> {
static <T> Streamable<T> empty() {
return Collections::emptyIterator;
}
@SafeVarargs
static <T> Streamable<T> of(T... t) {
return () -> {
return Arrays.asList(t).iterator();
};
}
static <T> Streamable<T> of(Iterable<T> iterable) {
Assert.notNull(iterable, "Iterable must not be null");
Objects.requireNonNull(iterable);
return iterable::iterator;
}
static <T> Streamable<T> of(Supplier<? extends Stream<T>> supplier) {
return LazyStreamable.of(supplier);
}
default Stream<T> stream() {
return StreamSupport.stream(this.spliterator(), false);
}
default <R> Streamable<R> map(Function<? super T, ? extends R> mapper) {
Assert.notNull(mapper, "Mapping function must not be null");
return of(() -> {
return this.stream().map(mapper);
});
}
default <R> Streamable<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) {
Assert.notNull(mapper, "Mapping function must not be null");
return of(() -> {
return this.stream().flatMap(mapper);
});
}
default Streamable<T> filter(Predicate<? super T> predicate) {
Assert.notNull(predicate, "Filter predicate must not be null");
return of(() -> {
return this.stream().filter(predicate);
});
}
default boolean isEmpty() {
return !this.iterator().hasNext();
}
default Streamable<T> and(Supplier<? extends Stream<? extends T>> stream) {
Assert.notNull(stream, "Stream must not be null");
return of(() -> {
return Stream.concat(this.stream(), (Stream)stream.get());
});
}
default Streamable<T> and(T... others) {
Assert.notNull(others, "Other values must not be null");
return of(() -> {
return Stream.concat(this.stream(), Arrays.stream(others));
});
}
default Streamable<T> and(Iterable<? extends T> iterable) {
Assert.notNull(iterable, "Iterable must not be null");
return of(() -> {
return Stream.concat(this.stream(), StreamSupport.stream(iterable.spliterator(), false));
});
}
default Streamable<T> and(Streamable<? extends T> streamable) {
return this.and((Supplier)streamable);
}
default List<T> toList() {
return (List)this.stream().collect(StreamUtils.toUnmodifiableList());
}
default Set<T> toSet() {
return (Set)this.stream().collect(StreamUtils.toUnmodifiableSet());
}
default Stream<T> get() {
return this.stream();
}
static <S> Collector<S, ?, Streamable<S>> toStreamable() {
return toStreamable(Collectors.toList());
}
static <S, T extends Iterable<S>> Collector<S, ?, Streamable<S>> toStreamable(Collector<S, ?, T> intermediate) {
return Collector.of(intermediate.supplier(), intermediate.accumulator(), intermediate.combiner(), Streamable::of);
}
}
Iterable<T> 인터페이스package java.lang;
import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
/**
* Implementing this interface allows an object to be the target of the enhanced
* {@code for} statement (sometimes called the "for-each loop" statement).
*
* @param <T> the type of elements returned by the iterator
*
* @since 1.5
* @jls 14.14.2 The enhanced {@code for} statement
*/
public interface Iterable<T> {
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
Iterator<T> iterator();
/**
* Performs the given action for each element of the {@code Iterable}
* until all elements have been processed or the action throws an
* exception. Actions are performed in the order of iteration, if that
* order is specified. Exceptions thrown by the action are relayed to the
* caller.
* <p>
* The behavior of this method is unspecified if the action performs
* side-effects that modify the underlying source of elements, unless an
* overriding class has specified a concurrent modification policy.
*
* @implSpec
* <p>The default implementation behaves as if:
* <pre>{@code
* for (T t : this)
* action.accept(t);
* }</pre>
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
/**
* Creates a {@link Spliterator} over the elements described by this
* {@code Iterable}.
*
* @implSpec
* The default implementation creates an
* <em><a href="../util/Spliterator.html#binding">early-binding</a></em>
* spliterator from the iterable's {@code Iterator}. The spliterator
* inherits the <em>fail-fast</em> properties of the iterable's iterator.
*
* @implNote
* The default implementation should usually be overridden. The
* spliterator returned by the default implementation has poor splitting
* capabilities, is unsized, and does not report any spliterator
* characteristics. Implementing classes can nearly always provide a
* better implementation.
*
* @return a {@code Spliterator} over the elements described by this
* {@code Iterable}.
* @since 1.8
*/
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
Supplier<T> 인터페이스package java.util.function;
/**
* Represents a supplier of results.
*
* <p>There is no requirement that a new or distinct result be returned each
* time the supplier is invoked.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #get()}.
*
* @param <T> the type of results supplied by this supplier
*
* @since 1.8
*/
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
package org.springframework.data.domain;
import java.util.Optional;
import org.springframework.util.Assert;
public interface Pageable {
static Pageable unpaged() {
return unpaged(Sort.unsorted());
}
static Pageable unpaged(Sort sort) {
return Unpaged.sorted(sort);
}
static Pageable ofSize(int pageSize) {
return PageRequest.of(0, pageSize);
}
default boolean isPaged() {
return true;
}
default boolean isUnpaged() {
return !this.isPaged();
}
int getPageNumber();
int getPageSize();
long getOffset();
Sort getSort();
default Sort getSortOr(Sort sort) {
Assert.notNull(sort, "Fallback Sort must not be null");
return this.getSort().isSorted() ? this.getSort() : sort;
}
Pageable next();
Pageable previousOrFirst();
Pageable first();
Pageable withPage(int pageNumber);
boolean hasPrevious();
default Optional<Pageable> toOptional() {
return this.isUnpaged() ? Optional.empty() : Optional.of(this);
}
default Limit toLimit() {
return this.isUnpaged() ? Limit.unlimited() : Limit.of(this.getPageSize());
}
default OffsetScrollPosition toScrollPosition() {
if (this.isUnpaged()) {
throw new IllegalStateException("Cannot create OffsetScrollPosition from an unpaged instance");
} else {
return this.getOffset() > 0L ? ScrollPosition.offset(this.getOffset() - 1L) : ScrollPosition.offset();
}
}
}
상속계층도는 아래와 같음.
PageRequest 클래스.
↑
AbstractPageRequest 추상클래스
↑
Pageable, Serializable 인터페이스
package org.springframework.data.domain;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
public class PageRequest extends AbstractPageRequest {
private static final long serialVersionUID = -4541509938956089562L;
private final Sort sort;
protected PageRequest(int pageNumber, int pageSize, Sort sort) {
super(pageNumber, pageSize);
Assert.notNull(sort, "Sort must not be null");
this.sort = sort;
}
public static PageRequest of(int pageNumber, int pageSize) {
return of(pageNumber, pageSize, Sort.unsorted());
}
public static PageRequest of(int pageNumber, int pageSize, Sort sort) {
return new PageRequest(pageNumber, pageSize, sort);
}
public static PageRequest of(int pageNumber, int pageSize, Sort.Direction direction, String... properties) {
return of(pageNumber, pageSize, Sort.by(direction, properties));
}
public static PageRequest ofSize(int pageSize) {
return of(0, pageSize);
}
public Sort getSort() {
return this.sort;
}
public PageRequest next() {
return new PageRequest(this.getPageNumber() + 1, this.getPageSize(), this.getSort());
}
public PageRequest previous() {
return this.getPageNumber() == 0 ? this : new PageRequest(this.getPageNumber() - 1, this.getPageSize(), this.getSort());
}
public PageRequest first() {
return new PageRequest(0, this.getPageSize(), this.getSort());
}
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof PageRequest)) {
return false;
} else {
PageRequest that = (PageRequest)obj;
return super.equals(that) && this.sort.equals(that.sort);
}
}
public PageRequest withPage(int pageNumber) {
return new PageRequest(pageNumber, this.getPageSize(), this.getSort());
}
public PageRequest withSort(Sort.Direction direction, String... properties) {
return new PageRequest(this.getPageNumber(), this.getPageSize(), Sort.by(direction, properties));
}
public PageRequest withSort(Sort sort) {
return new PageRequest(this.getPageNumber(), this.getPageSize(), sort);
}
public int hashCode() {
return 31 * super.hashCode() + this.sort.hashCode();
}
public String toString() {
return String.format("Page request [number: %d, size %d, sort: %s]", this.getPageNumber(), this.getPageSize(), this.sort);
}
}
package org.springframework.data.domain;
import java.io.Serializable;
public abstract class AbstractPageRequest implements Pageable, Serializable {
private static final long serialVersionUID = 1232825578694716871L;
private final int pageNumber;
private final int pageSize;
public AbstractPageRequest(int pageNumber, int pageSize) {
if (pageNumber < 0) {
throw new IllegalArgumentException("Page index must not be less than zero");
} else if (pageSize < 1) {
throw new IllegalArgumentException("Page size must not be less than one");
} else {
this.pageNumber = pageNumber;
this.pageSize = pageSize;
}
}
public int getPageSize() {
return this.pageSize;
}
public int getPageNumber() {
return this.pageNumber;
}
public long getOffset() {
return (long)this.pageNumber * (long)this.pageSize;
}
public boolean hasPrevious() {
return this.pageNumber > 0;
}
public Pageable previousOrFirst() {
return this.hasPrevious() ? this.previous() : this.first();
}
public abstract Pageable next();
public abstract Pageable previous();
public abstract Pageable first();
public int hashCode() {
int prime = true;
int result = 1;
result = 31 * result + this.pageNumber;
result = 31 * result + this.pageSize;
return result;
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj != null && this.getClass() == obj.getClass()) {
AbstractPageRequest other = (AbstractPageRequest)obj;
return this.pageNumber == other.pageNumber && this.pageSize == other.pageSize;
} else {
return false;
}
}
}
package org.springframework.data.domain;
import java.util.Optional;
import org.springframework.util.Assert;
public interface Pageable {
static Pageable unpaged() {
return unpaged(Sort.unsorted());
}
static Pageable unpaged(Sort sort) {
return Unpaged.sorted(sort);
}
static Pageable ofSize(int pageSize) {
return PageRequest.of(0, pageSize);
}
default boolean isPaged() {
return true;
}
default boolean isUnpaged() {
return !this.isPaged();
}
int getPageNumber();
int getPageSize();
long getOffset();
Sort getSort();
default Sort getSortOr(Sort sort) {
Assert.notNull(sort, "Fallback Sort must not be null");
return this.getSort().isSorted() ? this.getSort() : sort;
}
Pageable next();
Pageable previousOrFirst();
Pageable first();
Pageable withPage(int pageNumber);
boolean hasPrevious();
default Optional<Pageable> toOptional() {
return this.isUnpaged() ? Optional.empty() : Optional.of(this);
}
default Limit toLimit() {
return this.isUnpaged() ? Limit.unlimited() : Limit.of(this.getPageSize());
}
default OffsetScrollPosition toScrollPosition() {
if (this.isUnpaged()) {
throw new IllegalStateException("Cannot create OffsetScrollPosition from an unpaged instance");
} else {
return this.getOffset() > 0L ? ScrollPosition.offset(this.getOffset() - 1L) : ScrollPosition.offset();
}
}
}
public interface Serializable {
}
TypedSort<T>, Order 내부 클래스(Inner Class)가 존재.Direction, NullHandling 열거형(Enum)이 존재.상속계층도는 아래와 같음.
Sort 클래스
↑
Streamable<T> 인터페이스,Serializable 인터페이스
↑
Iterable<T> 인터페이스,Supplier<Stream<T>> 인터페이스
package org.springframework.data.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.data.util.MethodInvocationRecorder;
import org.springframework.data.util.Streamable;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
public class Sort implements Streamable<Order>, Serializable {
private static final long serialVersionUID = 5737186511678863905L;
private static final Sort UNSORTED = by();
public static final Direction DEFAULT_DIRECTION;
private final List<Order> orders;
protected Sort(List<Order> orders) {
this.orders = orders;
}
private Sort(Direction direction, @Nullable List<String> properties) {
if (properties != null && !properties.isEmpty()) {
this.orders = (List)properties.stream().map((it) -> {
return new Order(direction, it);
}).collect(Collectors.toList());
} else {
throw new IllegalArgumentException("You have to provide at least one property to sort by");
}
}
public static Sort by(String... properties) {
Assert.notNull(properties, "Properties must not be null");
return properties.length == 0 ? unsorted() : new Sort(DEFAULT_DIRECTION, Arrays.asList(properties));
}
public static Sort by(List<Order> orders) {
Assert.notNull(orders, "Orders must not be null");
return orders.isEmpty() ? unsorted() : new Sort(orders);
}
public static Sort by(Order... orders) {
Assert.notNull(orders, "Orders must not be null");
return new Sort(Arrays.asList(orders));
}
public static Sort by(Direction direction, String... properties) {
Assert.notNull(direction, "Direction must not be null");
Assert.notNull(properties, "Properties must not be null");
Assert.isTrue(properties.length > 0, "At least one property must be given");
return by((List)Arrays.stream(properties).map((it) -> {
return new Order(direction, it);
}).collect(Collectors.toList()));
}
public static <T> TypedSort<T> sort(Class<T> type) {
return new TypedSort(type);
}
public static Sort unsorted() {
return UNSORTED;
}
public Sort descending() {
return this.withDirection(Sort.Direction.DESC);
}
public Sort ascending() {
return this.withDirection(Sort.Direction.ASC);
}
public boolean isSorted() {
return !this.isEmpty();
}
public boolean isEmpty() {
return this.orders.isEmpty();
}
public boolean isUnsorted() {
return !this.isSorted();
}
public Sort and(Sort sort) {
Assert.notNull(sort, "Sort must not be null");
List<Order> these = new ArrayList(this.toList());
Iterator var3 = sort.iterator();
while(var3.hasNext()) {
Order order = (Order)var3.next();
these.add(order);
}
return by((List)these);
}
public Sort reverse() {
List<Order> reversed = this.doReverse();
return by(reversed);
}
protected List<Order> doReverse() {
List<Order> reversed = new ArrayList(this.orders.size());
Iterator var2 = this.iterator();
while(var2.hasNext()) {
Order order = (Order)var2.next();
reversed.add(order.reverse());
}
return reversed;
}
@Nullable
public Order getOrderFor(String property) {
Iterator var2 = this.iterator();
Order order;
do {
if (!var2.hasNext()) {
return null;
}
order = (Order)var2.next();
} while(!order.getProperty().equals(property));
return order;
}
public Iterator<Order> iterator() {
return this.orders.iterator();
}
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof Sort) {
Sort that = (Sort)obj;
return this.toList().equals(that.toList());
} else {
return false;
}
}
public int hashCode() {
int result = 17;
result = 31 * result + this.orders.hashCode();
return result;
}
public String toString() {
return this.isEmpty() ? "UNSORTED" : StringUtils.collectionToCommaDelimitedString(this.orders);
}
private Sort withDirection(Direction direction) {
List<Order> result = new ArrayList(this.orders.size());
Iterator var3 = this.iterator();
while(var3.hasNext()) {
Order order = (Order)var3.next();
result.add(order.with(direction));
}
return by((List)result);
}
static {
DEFAULT_DIRECTION = Sort.Direction.ASC;
}
public static enum Direction {
ASC,
DESC;
private Direction() {
}
public boolean isAscending() {
return this.equals(ASC);
}
public boolean isDescending() {
return this.equals(DESC);
}
public static Direction fromString(String value) {
try {
return valueOf(value.toUpperCase(Locale.US));
} catch (Exception var2) {
throw new IllegalArgumentException(String.format("Invalid value '%s' for orders given; Has to be either 'desc' or 'asc' (case insensitive)", value), var2);
}
}
public static Optional<Direction> fromOptionalString(String value) {
if (ObjectUtils.isEmpty(value)) {
return Optional.empty();
} else {
try {
return Optional.of(fromString(value));
} catch (IllegalArgumentException var2) {
return Optional.empty();
}
}
}
}
public static class TypedSort<T> extends Sort {
private static final long serialVersionUID = -3550403511206745880L;
private final MethodInvocationRecorder.Recorded<T> recorded;
private TypedSort(Class<T> type) {
this(MethodInvocationRecorder.forProxyOf(type));
}
private TypedSort(MethodInvocationRecorder.Recorded<T> recorded) {
super(Collections.emptyList());
this.recorded = recorded;
}
public <S> TypedSort<S> by(Function<T, S> property) {
return new TypedSort(this.recorded.record(property));
}
public <S> TypedSort<S> by(MethodInvocationRecorder.Recorded.ToCollectionConverter<T, S> collectionProperty) {
return new TypedSort(this.recorded.record(collectionProperty));
}
public <S> TypedSort<S> by(MethodInvocationRecorder.Recorded.ToMapConverter<T, S> mapProperty) {
return new TypedSort(this.recorded.record(mapProperty));
}
public Sort ascending() {
return this.withDirection(Sort::ascending);
}
public Sort descending() {
return this.withDirection(Sort::descending);
}
private Sort withDirection(Function<Sort, Sort> direction) {
return (Sort)this.recorded.getPropertyPath().map((xva$0) -> {
return Sort.by(xva$0);
}).map(direction).orElseGet(Sort::unsorted);
}
public Iterator<Order> iterator() {
return ((Set)this.recorded.getPropertyPath().map(Order::by).map(Collections::singleton).orElseGet(Collections::emptySet)).iterator();
}
public boolean isEmpty() {
return this.recorded.getPropertyPath().isEmpty();
}
public String toString() {
return ((Sort)this.recorded.getPropertyPath().map((xva$0) -> {
return Sort.by(xva$0);
}).orElseGet(Sort::unsorted)).toString();
}
}
public static class Order implements Serializable {
private static final long serialVersionUID = 1522511010900108987L;
private static final boolean DEFAULT_IGNORE_CASE = false;
private static final NullHandling DEFAULT_NULL_HANDLING;
private final Direction direction;
private final String property;
private final boolean ignoreCase;
private final NullHandling nullHandling;
public Order(@Nullable Direction direction, String property) {
this(direction, property, false, DEFAULT_NULL_HANDLING);
}
public Order(@Nullable Direction direction, String property, NullHandling nullHandlingHint) {
this(direction, property, false, nullHandlingHint);
}
public Order(@Nullable Direction direction, String property, boolean ignoreCase, NullHandling nullHandling) {
if (!StringUtils.hasText(property)) {
throw new IllegalArgumentException("Property must not be null or empty");
} else {
this.direction = direction == null ? Sort.DEFAULT_DIRECTION : direction;
this.property = property;
this.ignoreCase = ignoreCase;
this.nullHandling = nullHandling;
}
}
public static Order by(String property) {
return new Order(Sort.DEFAULT_DIRECTION, property);
}
public static Order asc(String property) {
return new Order(Sort.Direction.ASC, property, DEFAULT_NULL_HANDLING);
}
public static Order desc(String property) {
return new Order(Sort.Direction.DESC, property, DEFAULT_NULL_HANDLING);
}
public Direction getDirection() {
return this.direction;
}
public String getProperty() {
return this.property;
}
public boolean isAscending() {
return this.direction.isAscending();
}
public boolean isDescending() {
return this.direction.isDescending();
}
public boolean isIgnoreCase() {
return this.ignoreCase;
}
public Order with(Direction direction) {
return new Order(direction, this.property, this.ignoreCase, this.nullHandling);
}
public Order reverse() {
return this.with(this.direction == Sort.Direction.ASC ? Sort.Direction.DESC : Sort.Direction.ASC);
}
public Order withProperty(String property) {
return new Order(this.direction, property, this.ignoreCase, this.nullHandling);
}
public Sort withProperties(String... properties) {
return Sort.by(this.direction, properties);
}
public Order ignoreCase() {
return new Order(this.direction, this.property, true, this.nullHandling);
}
public Order with(NullHandling nullHandling) {
return new Order(this.direction, this.property, this.ignoreCase, nullHandling);
}
public Order nullsFirst() {
return this.with(Sort.NullHandling.NULLS_FIRST);
}
public Order nullsLast() {
return this.with(Sort.NullHandling.NULLS_LAST);
}
public Order nullsNative() {
return this.with(Sort.NullHandling.NATIVE);
}
public NullHandling getNullHandling() {
return this.nullHandling;
}
public int hashCode() {
int result = 17;
result = 31 * result + this.direction.hashCode();
result = 31 * result + this.property.hashCode();
result = 31 * result + (this.ignoreCase ? 1 : 0);
result = 31 * result + this.nullHandling.hashCode();
return result;
}
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof Order)) {
return false;
} else {
Order that = (Order)obj;
return this.direction.equals(that.direction) && this.property.equals(that.property) && this.ignoreCase == that.ignoreCase && this.nullHandling.equals(that.nullHandling);
}
}
public String toString() {
String result = String.format("%s: %s", this.property, this.direction);
if (!Sort.NullHandling.NATIVE.equals(this.nullHandling)) {
result = result + ", " + this.nullHandling;
}
if (this.ignoreCase) {
result = result + ", ignoring case";
}
return result;
}
static {
DEFAULT_NULL_HANDLING = Sort.NullHandling.NATIVE;
}
}
public static enum NullHandling {
NATIVE,
NULLS_FIRST,
NULLS_LAST;
private NullHandling() {
}
}
}