如何正确使用 Spring Data 中的 PagedResourcesAssembler?
Posted
技术标签:
【中文标题】如何正确使用 Spring Data 中的 PagedResourcesAssembler?【英文标题】:How to correctly use PagedResourcesAssembler from Spring Data? 【发布时间】:2014-02-16 06:09:42 【问题描述】:我正在使用 Spring 4.0.0.RELEASE、Spring Data Commons 1.7.0.M1、Spring Hateoas 0.8.0.RELEASE
我的资源是一个简单的 POJO:
public class UserResource extends ResourceSupport ...
我的资源组装器将 User 对象转换为 UserResource 对象:
@Component
public class UserResourceAssembler extends ResourceAssemblerSupport<User, UserResource>
public UserResourceAssembler()
super(UserController.class, UserResource.class);
@Override
public UserResource toResource(User entity)
// map User to UserResource
在我的 UserController 中,我想从我的服务中检索 Page<User>
,然后使用 PagedResourcesAssembler
将其转换为 PagedResources<UserResource>
,如下所示:https://***.com/a/16794740/1321564
@RequestMapping(value="", method=RequestMethod.GET)
PagedResources<UserResource> get(@PageableDefault Pageable p, PagedResourcesAssembler assembler)
Page<User> u = service.get(p)
return assembler.toResource(u);
这不会调用UserResourceAssembler
,只是返回User
的内容,而不是我的自定义UserResource
。
返回单个资源有效:
@Autowired
UserResourceAssembler assembler;
@RequestMapping(value="id", method=RequestMethod.GET)
UserResource getById(@PathVariable ObjectId id) throws NotFoundException
return assembler.toResource(service.getById(id));
PagedResourcesAssembler
需要一些通用参数,但是我不能使用 T toResource(T)
,因为我不想将我的 Page<User>
转换为 PagedResources<User>
,尤其是因为 User
是 POJO 而不是资源。
所以问题是:它是如何工作的?
编辑:
我的 WebMvcConfigurationSupport:
@Configuration
@ComponentScan
@EnableHypermediaSupport
public class WebMvcConfig extends WebMvcConfigurationSupport
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers)
argumentResolvers.add(pageableResolver());
argumentResolvers.add(sortResolver());
argumentResolvers.add(pagedResourcesAssemblerArgumentResolver());
@Bean
public HateoasPageableHandlerMethodArgumentResolver pageableResolver()
return new HateoasPageableHandlerMethodArgumentResolver(sortResolver());
@Bean
public HateoasSortHandlerMethodArgumentResolver sortResolver()
return new HateoasSortHandlerMethodArgumentResolver();
@Bean
public PagedResourcesAssembler<?> pagedResourcesAssembler()
return new PagedResourcesAssembler<Object>(pageableResolver(), null);
@Bean
public PagedResourcesAssemblerArgumentResolver pagedResourcesAssemblerArgumentResolver()
return new PagedResourcesAssemblerArgumentResolver(pageableResolver(), null);
/* ... */
解决方案:
@Autowired
UserResourceAssembler assembler;
@RequestMapping(value="", method=RequestMethod.GET)
PagedResources<UserResource> get(@PageableDefault Pageable p, PagedResourcesAssembler pagedAssembler)
Page<User> u = service.get(p)
return pagedAssembler.toResource(u, assembler);
【问题讨论】:
【参考方案1】:您似乎已经找到了正确的使用方法,但我想在这里详细介绍一些细节,以便其他人也能找到。我在this answer 中详细介绍了PagedResourceAssembler
。
表示模型
Spring HATEOAS 附带了各种表示模型的基类,可以轻松创建配备链接的表示。提供了三种开箱即用的类:
Resource
- 物品资源。有效地包裹一些 DTO 或实体,这些 DTO 或实体捕获一个单个项并通过链接丰富它。
Resources
- 集合资源,可以是某些东西的集合,但通常是 Resource
实例的集合。
PagedResources
- Resources
的扩展,可捕获额外的分页信息,例如总页数等。
所有这些类都派生自ResourceSupport
,这是Link
实例的基本容器。
资源组装器
ResourceAssembler
现在是将域对象或 DTO 转换为此类资源实例的缓解组件。这里重要的部分是,它将 one 源对象转换为 one 目标对象。
因此,PagedResourcesAssembler
将采用 Spring Data Page
实例并将其转换为 PagedResources
实例,方法是评估 Page
并创建必要的 PageMetadata
以及 prev
和 next
导航页面的链接。默认情况下 - 这可能是这里有趣的部分 - 它将使用普通的 SimplePagedResourceAssembler
(PRA
的内部类)将页面的各个元素转换为嵌套的 Resource
实例。
为了允许对此进行自定义,PRA
具有额外的 toResource(…)
方法,这些方法采用委托 ResourceAssembler
来处理各个项目。所以你最终会得到这样的结果:
class UserResource extends ResourceSupport …
class UserResourceAssembler extends ResourceAssemblerSupport<User, UserResource> …
客户端代码现在看起来像这样:
PagedResourcesAssembler<User> parAssembler = … // obtain via DI
UserResourceAssembler userResourceAssembler = … // obtain via DI
Page<User> users = userRepository.findAll(new PageRequest(0, 10));
// Tell PAR to use the user assembler for individual items.
PagedResources<UserResource> pagedUserResource = parAssembler.toResource(
users, userResourceAssembler);
展望
从即将发布的 Spring Data Commons 1.7 RC1(和 Spring HATEOAS 0.9 过渡)开始,prev
和 next
链接将生成为符合 RFC6540 的 URI 模板,以公开在 HandlerMethodArgumentResolvers
中配置的分页请求参数对于Pageable
和Sort
。
您可以通过使用 @EnableSpringDataWebSupport
注释配置类来简化上面显示的配置,这样您就可以摆脱所有显式的 bean 声明。
【讨论】:
嗨,你能告诉我如何在PagedResourcesAssembler
中正确使用这些泛型吗?我只能将其用作原始类型。它说PagedResources<R> toResource(Page<T>, ResourceAssemblerSupport<T, R>)
。 Eclipse 不会接受这个:PagedResources<UserResource> p = parAssembler.toResource(userPage, userResourceAssembler)
.
我在上面的答案中添加了PagedResourcesAssembler
的泛型。如果您提供自定义UserResourceAssembler
即ResourceAssembler<User, UserResource>
,则需要注入PagedResourcesAssembler<User>
以最终创建PagedResources<UserResource>
。我刚刚在代码库中添加了一个test case,给出了一个工作示例。
面对同样的问题,在 Spring 4.0.6.RELEASE 和 0.16.0.RELEASE 上,toResource 只接受一个参数,adminResourcePages,并且无法传入自定义的 adminResourceAssembler。这是方法签名: toResource(Page, Link) 我在这里使用了错误的 API 吗?
我现在知道了: Page我想将资源列表转换为页面。但是当给它 PagedResourcesAssembler 时,它正在吃掉内部链接。
这将使您的列表分页。
public class JobExecutionInfoResource extends ResourceSupport
private final JobExecutionInfo jobExecution;
public JobExecutionInfoResource(final JobExecutionInfo jobExecution)
this.jobExecution = jobExecution;
add(ControllerLinkBuilder.linkTo(methodOn(JobsMonitorController.class).get(jobExecution.getId())).withSelfRel()); // add your own links.
public JobExecutionInfo getJobExecution()
return jobExecution;
分页资源提供 ResourceAssembler 告诉分页资源使用它,它什么也不做,只是简单地返回它,因为它已经是一个被传递的资源列表。
private final PagedResourcesAssembler<JobExecutionInfoResource> jobExecutionInfoResourcePagedResourcesAssembler;
public static final PageRequest DEFAULT_PAGE_REQUEST = new PageRequest(0, 20);
public static final ResourceAssembler<JobExecutionInfoResource, JobExecutionInfoResource> SIMPLE_ASSEMBLER = entity -> entity;
@GetMapping("/clientCode/propertyCode/summary")
public PagedResources<JobExecutionInfoResource> getJobsSummary(@PathVariable String clientCode, @PathVariable String propertyCode,
@RequestParam(required = false) String exitStatus,
@RequestParam(required = false) String jobName,
Pageable pageRequest)
List<JobExecutionInfoResource> listOfResources = // your code to generate the list of resource;
int totalCount = 10// some code to get total count;
Link selfLink = linkTo(methodOn(JobsMonitorController.class).getJobsSummary(clientCode, propertyCode, exitStatus, jobName, DEFAULT_PAGE_REQUEST)).withSelfRel();
Page<JobExecutionInfoResource> page = new PageImpl<>(jobExecutions, pageRequest, totalCount);
return jobExecutionInfoResourcePagedResourcesAssembler.toResource(page, SIMPLE_ASSEMBLER, selfLink);
【讨论】:
【参考方案3】:替代方式
另一种方法是使用 Range HTTP 标头(在RFC 7233 中阅读更多内容)。您可以这样定义 HTTP 标头:
Range: resources=20-41
这意味着,您想要从 20 到 41(包括)获取资源。这种方式允许 API 的消费者接收到精确定义的资源。
这只是另一种方式。范围通常与另一个单位(如字节等)一起使用。
推荐方式
如果您想使用分页功能并拥有真正适用的 API(包括超媒体/HATEOAS),那么我建议您将 Page 和 PageSize 添加到您的 URL。示例:
http://host.loc/articles?Page=1&PageSize=20
然后,您可以在 BaseApiController 中读取此数据并在所有请求中创建一些 QueryFilter 对象:
var requestHelper = new RequestHelper(Request);
int page = requestHelper.GetValueFromQueryString<int>("page");
int pageSize = requestHelper.GetValueFromQueryString<int>("pagesize");
var filter = new QueryFilter
Page = page != 0 ? page : DefaultPageNumber,
PageSize = pageSize != 0 ? pageSize : DefaultPageSize
;
return filter;
您的 api 应该返回一些包含项目数量信息的特殊集合。
public class ApiCollection<T>
public ApiCollection()
Data = new List<T>();
public ApiCollection(int? totalItems, int? totalPages)
Data = new List<T>();
TotalItems = totalItems;
TotalPages = totalPages;
public IEnumerable<T> Data get; set;
public int? TotalItems get; set;
public int? TotalPages get; set;
你的模型类可以继承一些支持分页的类:
public abstract class ApiEntity
public List<ApiLink> Links get; set;
public class ApiLink
public ApiLink(string rel, string href)
Rel = rel;
Href = href;
public string Href get; set;
public string Rel get; set;
【讨论】:
你的回答与SpringsPagedResourcesAssembler
无关。模型类不应该继承任何 API 的东西。 ...
关于Range
:完全不推荐!生成的 URL 不是幂等的,但 GET 请求应该是。这意味着,我可以使用不同的 Range
标头调用相同的 URL 并获得不同的结果。 --- 你不能复制粘贴 URL 得到相同的结果,因为你总是会丢失Range
信息。
1) 同意,我只是展示了另一种方式... Range 通常用于流式传输 api
2) 同意,我的示例不包含任何数据模型类,只有 API 模型类以上是关于如何正确使用 Spring Data 中的 PagedResourcesAssembler?的主要内容,如果未能解决你的问题,请参考以下文章
使用 Spring Data 中的 CassandraRepository 为 Cassandra 实现分页的正确方法
如何在 Spring data jpa 中正确使用 findBySomeOtherId 而不是 findById?
如何找到正确的 Spring Data JPA 和 Spring 版本的 jar 文件
spring-data-jpa 如何使用多个数据源? [复制]