Автор оригинала: Eugen Paraschiv.
1. Обзор
Этот учебник будет посвящен внедрение pagination в API REST с использованием Spring MVC и Spring Data.
Дальнейшее чтение:
Пагинация с весенним REST и таблицей AngularJS
JPA Pagination
Обнаружение API REST и HATEOAS
2. Страница как ресурс против страницы как представление
Первый вопрос при проектировании пагинации в контексте архитектуры RESTful заключается в том, следует ли рассматривать страница фактического ресурса или просто представление ресурсов .
Рассматривая сам страницу как ресурс вводит множество проблем, таких как не в состоянии однозначно определить ресурсы между вызовами. Это, в сочетании с тем фактом, что в слое сохранения страница не является надлежащей сущностью, а держателем, который построен при необходимости, делает выбор простым: страница является частью представления .
Следующим вопросом в дизайне pagination в контексте REST является где включить информационный :
- на пути URI: /foo/page/1
- запрос URI: /foo?page-1
Имея в виду, что страница не является ресурсным , кодирование информации о странице в URI больше не является вариантом.
Мы собираемся использовать стандартный способ решения этой проблемы, кодирование информации paging в запросе URI.
3. Контроллер
Теперь, для реализации – Контроллер весеннего MVC для pagination прост :
@GetMapping(params = { "page", "size" }) public ListfindPaginated(@RequestParam("page") int page, @RequestParam("size") int size, UriComponentsBuilder uriBuilder, HttpServletResponse response) { Page resultPage = service.findPaginated(page, size); if (page > resultPage.getTotalPages()) { throw new MyResourceNotFoundException(); } eventPublisher.publishEvent(new PaginatedResultsRetrievedEvent ( Foo.class, uriBuilder, response, page, resultPage.getTotalPages(), size)); return resultPage.getContent(); }
В этом примере мы вводим два параметра запроса, в том размер и страница, в методе Контролера через @RequestParam.
Кроме того, мы могли бы использовать Страница объект, который отображает страница , размер , и сортировать параметров автоматически. Кроме того, PagingAndSortingRepository компания предоставляет нестандартные методы поддержки с помощью Страница в качестве параметра, а также.
Мы также инъекционных как Http ответ и UriComponentsBuilder , чтобы помочь с Discoverability – которые мы разъединения через пользовательские события. Если это не является целью API, можно просто удалить пользовательское событие.
Наконец – обратите внимание, что в центре внимания этой статьи только REST и веб-слой – пойти глубже в части доступа к данным pagination вы можете проверить эту статью о Pagination с весенними данными.
4. Открытие для REST Pagination
В рамках pagination, удовлетворяя HATEOAS ограничение REST означает, что клиент API может обнаружить в следующем и предыдущие страниц на основе текущей страницы навигации. С этой целью мы будем использовать Ссылка ЗАголовок HTTP, в сочетании с “ в следующем “, “ prev “, “ первый ” и “ последний ” типы ссылок .
В REST, Обнаруживаемость является перекрестной проблемой применимы не только к конкретным операциям, но и к видам операций. Например, каждый раз, когда создается Ресурс, URI этого ресурса должен быть обнаруживается клиентом. Поскольку это требование имеет отношение к созданию любого ресурса, мы будем обрабатывать его отдельно.
Мы разъединим эти проблемы с помощью событий, как мы обсуждали в предыдущая статья, посвященная доступности службы REST. В случае pagination, событие – PaginatedResultsRetrievedEvent – выстрелил в слой контроллера. Затем мы будем реализовывать обнаруживаемость с пользовательским слушателем для этого события.
Короче говоря, слушатель проверит, допускает ли навигация в следующем , предыдущие , первый и последний Страниц. Если это произойдет – это добавить соответствующие ИРИС к ответу в качестве заголовка «Ссылка» http .
Давайте шаг за шагом. UriComponentsBuilder переданный от контроллера содержит только базовый URL (хост, порт и контекстный путь). Таким образом, мы должны добавить оставшиеся разделы:
void addLinkHeaderOnPagedResourceRetrieval( UriComponentsBuilder uriBuilder, HttpServletResponse response, Class clazz, int page, int totalPages, int size ){ String resourceName = clazz.getSimpleName().toString().toLowerCase(); uriBuilder.path( "/admin/" + resourceName ); // ... }
Далее, мы будем использовать СтрингДжойнер для конкатенации каждого звена. Мы будем использовать uriBuilder для создания ИРИС. Давайте посмотрим, как мы будем действовать со ссылкой на в следующем страница:
StringJoiner linkHeader = new StringJoiner(", "); if (hasNextPage(page, totalPages)){ String uriForNextPage = constructNextPageUri(uriBuilder, page, size); linkHeader.add(createLinkHeader(uriForNextPage, "next")); }
Давайте посмотрим на логику конструкцияNextPageUri метод:
String constructNextPageUri(UriComponentsBuilder uriBuilder, int page, int size) { return uriBuilder.replaceQueryParam(PAGE, page + 1) .replaceQueryParam("size", size) .build() .encode() .toUriString(); }
Мы будем действовать аналогичным образом для остальных URIs, которые мы хотим включить.
Наконец, мы добавим выход в качестве заголовка ответа:
response.addHeader("Link", linkHeader.toString());
Обратите внимание, что для краткости, я включил только частичный образец кода и полный код здесь .
5. Тест вождения Pagination
И основная логика pagination, и обнаруживаемость охватываются небольшими, сфокусированными интеграционными тестами. Как и в предыдущая статья , мы будем использовать БИБЛИОТЕКА, гарантированная REST для потребления службы REST и проверки результатов.
Вот несколько примеров пагинации интеграционных тестов; для полного набора тестов ознакомьтесь с проектом GitHub (ссылка в конце статьи):
@Test public void whenResourcesAreRetrievedPaged_then200IsReceived(){ Response response = RestAssured.get(paths.getFooURL() + "?page=0&size=2"); assertThat(response.getStatusCode(), is(200)); } @Test public void whenPageOfResourcesAreRetrievedOutOfBounds_then404IsReceived(){ String url = getFooURL() + "?page=" + randomNumeric(5) + "&size=2"; Response response = RestAssured.get.get(url); assertThat(response.getStatusCode(), is(404)); } @Test public void givenResourcesExist_whenFirstPageIsRetrieved_thenPageContainsResources(){ createResource(); Response response = RestAssured.get(paths.getFooURL() + "?page=0&size=2"); assertFalse(response.body().as(List.class).isEmpty()); }
6. Тест вождения Pagination Открытие
Тестирование, что pagination обнаруживается клиентом является относительно простым, хотя есть много земли для покрытия.
Тесты будут сосредоточены на положении текущей страницы в навигационном и различные УРИ, которые должны быть обнаружимы с каждой позиции:
@Test public void whenFirstPageOfResourcesAreRetrieved_thenSecondPageIsNext(){ Response response = RestAssured.get(getFooURL()+"?page=0&size=2"); String uriToNextPage = extractURIByRel(response.getHeader("Link"), "next"); assertEquals(getFooURL()+"?page=1&size=2", uriToNextPage); } @Test public void whenFirstPageOfResourcesAreRetrieved_thenNoPreviousPage(){ Response response = RestAssured.get(getFooURL()+"?page=0&size=2"); String uriToPrevPage = extractURIByRel(response.getHeader("Link"), "prev"); assertNull(uriToPrevPage ); } @Test public void whenSecondPageOfResourcesAreRetrieved_thenFirstPageIsPrevious(){ Response response = RestAssured.get(getFooURL()+"?page=1&size=2"); String uriToPrevPage = extractURIByRel(response.getHeader("Link"), "prev"); assertEquals(getFooURL()+"?page=0&size=2", uriToPrevPage); } @Test public void whenLastPageOfResourcesIsRetrieved_thenNoNextPageIsDiscoverable(){ Response first = RestAssured.get(getFooURL()+"?page=0&size=2"); String uriToLastPage = extractURIByRel(first.getHeader("Link"), "last"); Response response = RestAssured.get(uriToLastPage); String uriToNextPage = extractURIByRel(response.getHeader("Link"), "next"); assertNull(uriToNextPage); }
Обратите внимание, что полный низкоуровневый код для extractURIByRel – ответственный за извлечение ИРИС rel отношения здесь .
7. Получение всех ресурсов
На ту же тему pagination и обнаруживаемости, выбор должен быть сделан, если клиенту разрешено получить все ресурсы в системе сразу, или если клиент должен попросить их paginated .
Если сделан выбор, что клиент не может получить все ресурсы с одного запроса, и pagination не является необязательным, но требуется, то несколько вариантов доступны для ответа на получить все запросы. Один из вариантов заключается в том, чтобы вернуть 404 ( Не найдено ) и использовать Ссылка заголовок, чтобы сделать первую страницу обнаружить:
Ссылка
Другой вариант – вернуть перенаправление – 303 (См. другие ) – на первую страницу. Более консервативным маршрутом было бы просто вернуть клиенту 405 ( Метод не допускается) для запроса GET.
8. REST Paging с диапазоном HTTP Хедерс
Относительно другой способ реализации pagination заключается в работе с HTTP Диапазон головки – Диапазон , Контент-диапазон , If-Range , Принять-Диапазоны – и Коды статуса HTTP – 206 ( Частичное содержание ), 413 ( Запрос сущность слишком большой ), 416 ( Запрошенный диапазон не удовлетворяется ).
Один из представлений об этом подходе заключается в том, что расширения диапазона HTTP не предназначены для pagination и что ими должен управлять Сервер, а не Приложение. Реализация pagination на основе расширений заголовка HTTP Range, тем не менее, технически возможна, хотя и не так часто, как реализация, обсуждаемая в этой статье.
9. Весенние данные REST Pagination
В весенних данных, если нам нужно вернуть несколько результатов из полного набора данных, мы можем использовать любую Страница метод репозитория, так как он всегда будет возвращать страница. Результаты будут возвращены в зависимости от количества страницы, размера страницы и направления сортировки.
Весенние данные REST автоматически распознает параметры URL, такие как страница, размер, сортировка и так далее.
Для использования методов paging любого репозитория нам необходимо расширить PagingAndSortingRepository:
public interface SubjectRepository extends PagingAndSortingRepository{}
Если мы позвоним http://localhost:8080/subjects Весна автоматически добавляет страница, размер, сортировка параметры предложения с API:
"_links" : { "self" : { "href" : "http://localhost:8080/subjects{?page,size,sort}", "templated" : true } }
По умолчанию размер страницы составляет 20, но мы можем изменить его, позвонив что-то вроде http://localhost:8080/subjects?page=10.
Если мы хотим внедрить paging в наш собственный пользовательский API репозитория, мы должны пройти дополнительный Страница параметр и убедитесь, что API возвращает страница:
@RestResource(path = "nameContains") public PagefindByNameContaining(@Param("name") String name, Pageable p);
Всякий раз, когда мы добавляем пользовательский API /поиск конечная точка добавляется к генерируемым ссылкам. Так что, если мы позвоним http://localhost:8080/subjects/search мы увидим, pagination способны конечной точки:
"findByNameContaining" : { "href" : "http://localhost:8080/subjects/search/nameContains{?name,page,size,sort}", "templated" : true }
Все API, которые реализуют PagingAndSortingRepository вернет страница. Если нам нужно вернуть список результатов с Страница, getContent () API Страница предоставляет список записей, полученных в результате API Spring Data REST.
Код в этом разделе доступен в весна-данные-отдых проект.
10. Преобразование списка в Страницу
Допустим, у нас есть Страница объект в качестве ввода, но информация, которую нам нужно получить, содержится в списке, а не PagingAndSortingRepository . В этих случаях нам, возможно, придется конвертировать Список в Страница .
Например, представьте, что у нас есть список результатов от службы SOAP:
Listlist = getListOfFooFromSoapService();
Мы должны получить доступ к списку в конкретных позициях, определенных Страница объект послал к нам. Итак, давайте определим стартовый индекс:
int start = (int) pageable.getOffset();
И конечный индекс:
int end = (int) ((start + pageable.getPageSize()) > fooList.size() ? fooList.size() : (start + pageable.getPageSize()));
Имея эти два на месте, мы можем создать Страница для получения списка элементов между ними:
Pagepage = new PageImpl (fooList.subList(start, end), pageable, fooList.size());
Ну вот! Мы можем вернуться сейчас страница как действительный результат.
И обратите внимание, что если мы также хотим, чтобы поддержать сортировка , мы должны сортировать список перед суб-листинг оно.
11. Заключение
Эта статья проиллюстрировала, как реализовать Pagination в API REST с помощью Spring, и обсудила, как настроить и протестировать Discoverability.
Если вы хотите пойти в глубину на pagination в уровне настойчивости, проверить мои JPA или Hibernate pagination учебники.
Реализация всех этих примеров и фрагментов кода можно найти в Проект GitHub – это Maven основе проекта, поэтому она должна быть легко импортировать и работать, как она есть.