春天顶部

通过参考资料,开始使用Spring 5和Spring Boot 2学习春天课程:

> >学习春天
休息前

通过参考资料,开始使用Spring 5和Spring Boot 2学习春天课程:

>>查看课程

1.概述

本教程将重点介绍使用Spring MVC和Spring数据在REST API中实现分页。

进一步阅读:

与春休息和angularjs表的分页

广泛地查看如何使用Spring分页和如何使用AngularJS和UI网格来实现简单API。

JPA分页

JPA中的分页——如何使用JQL和Criteria API正确地进行分页。

REST API可发现性和HATEOAS

Hateoas和REST服务的可发现性 - 由测试驱动。

2.页面作为资源vs页面作为表示

在RESTful体系结构上下文中设计分页时的第一个问题是是否考虑页面实际资源或只是资源的表示

作为资源处理页面本身引入了许多问题,例如不再能够在呼叫之间唯一地识别资源。这将与持久层中的事实相结合,页面不是一个适当的实体,而是必要时构造的持有者,使得选择简单:页面是表示的一部分

REST上下文中分页设计的下一个问题是其中包括寻呼信息:

  • 在URI路径中:/ foo / page / 1
  • URI查询:/ foo?页面= 1

请记住页面不是资源,编码URI中的页面信息不再是一个选项。

我们将使用解决这个问题的标准方式在URI查询中编码寻呼信息。

3.控制器

现在,对于实现用于分页的弹簧MVC控制器很简单:

@getmapping(params = {“页面”,“size”})公共列表 findpaginate(@requestparam(“页面”)int page,@requestparam(“size”)int size,uricomponentsbuilder uribuilder,httpservletresponse响应){页面结果= service.findpaginated(页面,大小);if(page>结果vage.gettotalpages()){抛出新的myresourcenotfoundException();} eventpublisher.publishevent(新的pagipationResultsretrivevent (foo.class,rubuilder,响应,页面,结果.gettotalpages(),size);返回结果vage.getContent();}

在此示例中,我们注入了两个查询参数,大小页,在控制器方法中通过@RequestParam。

或者,我们可以用a对象,地图页面,大小, 和种类自动参数。此外,pagingandsortingRepository.实体提供了支持使用的开箱即用的方法也是一个参数。

我们也注射了HTTP响应和UriComponentsBuilder以帮助发现-我们正在通过自定义事件解耦。如果这不是API的目标,您可以简单地删除自定义事件。

最后 - 请注意,本文的焦点只是其余的和网络层 - 更深入地进入数据访问部分的分页看看这篇文章金宝搏官网188be关于与春天数据分页。

4.REST分页的发现性

在分页的范围内,满足于此Hateoas休息的约束意味着使API的客户能够发现下一个以前的页面中基于当前页面的导航。为了这个目的,我们要用链接HTTP标题,耦合“下一个“,”上一页“,”第一的“ 和 ”去年“链接关系类型

休息,可发现性是一个横向关注的问题,不仅适用于特定操作,而是对运营的类型。例如,每次创建资源时,客户端都应该发现该资源的URI。由于此要求与创建任何资源相关,因此我们将单独处理。

我们将使用事件来解耦这些关注点,正如在关于可发现性的前一篇文章休息服务。在分页的情况下,事件 -PaginatedResultsRetrievedEvent-在控制器层触发。然后,我们将使用此事件的自定义侦听器实现可发现性。

简而言之,侦听器将检查导航是否允许a下一个,以前的,第一的去年页面。如果有,它会的将相关uri添加到响应中作为' Link' HTTP头

让我们一步一步走。这UriComponentsBuilder从控制器传递仅包含基本URL(主机,端口和上下文路径)。因此,我们必须添加剩下的部分:

void addlinkheaderonpagedResourceretrieval(UricomponentsBuilder uribuilder,httpservletresponse响应,类Clazz,Int页面,int TotalPages,Int大小){String ResourceName = Clazz.getSimpleName()。ToString()。TolowerCase();uribuilder.path(“/ admin /”+ resourcename);// ......}

接下来,我们将使用StringJoiner连接每个链接。我们将使用uribuilder.生成URI。让我们看看我们如何继续与之相关联下一个页:

stringjoiner linkheader = new stringjoiner(“,”);if(hasnextpage(页面,totalpages)){string urifornextpage = buildingnextpageuri(ulibuilder,页面,大小);linkheader.add(createLinkHeader(UrifornextPage,“Next”));}

让我们来看看逻辑buildingnextpageuri.方法:

String constructNextPageUri(UriComponentsBuilder urbuilder, int page, int size){返回urbuilder。replaceQueryParam(PAGE, PAGE + 1) .replaceQueryParam("size", size) .build() .encode() .toUriString();}

我们认为我们想要包含的其余URIS。

最后,我们将作为响应标题添加输出:

Response.addheader(“链接”,linkHeader.ToString());

请注意,对于简洁起见,我仅包括一个部分代码示例和这里的完整代码

5.测试驾驶分页

分页和发现的主要逻辑都包含在小型的集中集成测试中。就像在上一篇文章,我们将使用放心图书馆使用REST服务并验证结果。

这些是分页整合测试的一些例子;对于完整的测试套件,请查看GitHub项目(文章末尾的链接):

@Test public void whenresourcesareertrievedpaged_then200isreceived (){Response Response = restassurance .get(paths. getfouourl () + "?page=0&size=2");为了(response.getStatusCode (), (200);} @Test public void whenpageofresourcesareeretrievedoutofbounds_then404isreceived (){String url = getfourl () + "?page=" +随机数字(5)+ "&size=2";Response Response = RestAssured.get.get(url);为了(response.getStatusCode (), (404);} @Test public void givenResourcesExist_whenFirstPageIsRetrieved_thenPageContainsResources(){createResource();Response Response = RestAssured.get(paths. getfourl () + "?page=0&size=2");assertFalse (response.body()。as (List.class) .isEmpty ());}

6.测试驱动分页可发现性

测试客户端是否可以发现分页相对简单,尽管有很多内容需要讨论。

测试将专注于导航中当前页面的位置以及应该在每个位置都能发现的不同uri:

@test公共void时virstpageofresourcesareretrive_thensecondpageisnext(){response response = restassured.get(getfoourl()+“?page = 0&size = 2”);字符串uritonextpage = electrutibyrel(response.getheader(“链接”),“下一步”);assertequals(getfoourl()+“?page = 1&size = 2”,uritonextpage);@Test公共void时firstpageofresourcesareretrive_thennupleviouspage(){response response = RestAseSured.get(getFoourl()+“?页面= 0&size = 2”);字符串uritoprevpage = electrutibyrel(response.getheader(“链接”),“prev”);Assertnull(Uritopevpage);@test公共void whensecondpageofresourceareretrive_thenfirstpageisprevious(){response response = restassured.get(getfoourl()+“?page = 1&size = 2”);字符串uritoprevpage = electrutibyrel(response.getheader(“链接”),“prev”);assertequals(getfoourl()+“?page = 0&size = 2”,Uritoprevpage);@test公共void whenlastpageofresourcesisretrice_thennoonextpageisdiscoverable(){responst 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-负责抽取uri byrel关系在这儿

7.让所有资源

在同一主题的分页和可发现性,如果允许客户机一次检索系统中的所有资源,或者客户机必须要求对这些资源进行分页,则必须做出选择

如果选择客户端不能通过单个请求检索所有的Resources,并且分页不是可选的,而是必需的,那么对get all请求的响应有几个选项可用。一个选项是返回404 (未找到)使用链接页眉使第一页可发现:

链接= < http://localhost: 8080 / rest / api / admin / foo ? page = 0大小= 2 >;rel =“第一”,< http://localhost: 8080 / rest / api / admin / foo ? = 103页大小= 2 >;rel = "最后一次"

另一种选择是返回重定向 - 303(见其他) -到第一页。更保守的路线是简单地返回到客户的405 (不允许方法)获取GET请求。

8.其他分页,范围HTTP头信息

实现分页的相对不同的方式是与之合作HTTP范围标题- - - - - -范围,内容范围,IF范围,接受范围- - -HTTP状态代码- 206 (部分内容), 413 (请求的实体太大),416(要求范围不满足)。

对此方法的一个视图是HTTP系列扩展不是用于分页,并且它们应该由服务器管理而不是应用程序。然而,实现了基于HTTP范围标题扩展的分页,尽管与本文讨论的实现几乎是常见的。

9.弹簧数据休息分页

在Spring Data中,如果我们需要从完整的数据集中返回一些结果,我们可以使用任何结果存储库方法,因为它将永远返回一个页。结果将根据页码,页面大小和排序方向返回。

春天数据休息自动识别URL参数,如页面,大小,排序等等。

要使用我们需要扩展的任何存储库的分页方法pagingandsortingRepository:

公共界面主观图延伸了PagingandSortingRepository <主题,long> {}

如果我们打电话http:// localhost:8080 /科目Spring自动添加页面,大小,排序参数与API的建议:

“_links”:{“self”:{“href”:“http:// localhost:8080 / projects {?页面,size,sort}”,“templated”:true}}

默认情况下,页面大小为20,但我们可以通过调用类似的东西来改变它http://localhost: 8080 /科目? = 10页。

如果我们想实现分页到我们自己的自定义存储库API,我们需要额外传递参数,并确保API返回页:

@RestResource(path = "nameContains") public Page findbynamecontainain (@Param("name") String name, Pageable p);

每当我们添加一个自定义API/搜索端点添加到生成的链接中。所以,如果我们打电话http:// localhost:8080 / projects / search我们会看到一个能力的端点分页:

" findbynamecontainain ": {"href": "http://localhost:8080/subjects/search/nameContains{?Name,page,size,sort}", "template ": true}

所有实现的apipagingandsortingRepository.将返回A.页。如果我们需要返回结果的结果页,getContent()API页面提供由于Spring Data REST API而获取的记录列表。

本节中的代码可用春季数据休息项目。

10.转换A.列表进入A.页面

让我们假设我们有一个对象作为输入,但我们需要检索的信息包含在列表中而不是apagingandsortingRepository.。在这些情况下,我们可能需要把一个列表进入A.页面

例如,想象一下我们有一个结果列表肥皂服务:

List List = getListOfFooFromSoapService();

对象指定的特定位置上访问列表发送给我们的对象。所以,让我们定义开始索引:

int start = (int) pageable.getOffset();

和最终指数:

int end =(int)((start + pageable.getpagesize())> footist.size()?footist.size():( start + pageable.getpageSize())));

拥有这两个人,我们可以创造一个页面要获取它们之间的元素列表:

Page 页面= new pageimpl (foolist.sublist(start,结束),pagabled,foleist.size());

就是这样!我们现在可以返回页面作为有效的结果。

并注意,如果我们也想要支持排序,我们需要在子列表之前对列表进行排序它。

11.结论

本文说明了如何使用Spring,并讨论如何设置和测试可发现性的休息API中的分页。

如果你想在持久性水平中深入分散,请查看我的JPA.或者冬眠分页教程。

控件中可以找到所有这些示例和代码片段的实现GitHub项目- 这是一个基于Maven的项目,因此应该易于导入和运行。

弹簧底部

开始使用Spring 5和Spring Boot 2,通过学习春天课程:

>>课程
休息下

开始使用Spring 5和Spring Boot 2,通过学习春天课程 :

>>查看课程
评论在本文上关闭!