form example crudrepository spring spring-mvc pagination spring-data spring-hateoas

example - ¿Cómo usar correctamente PagedResourcesAssembler desde Spring Data?



spring tags (2)

Parece que ya has descubierto la forma correcta de usarlo, pero me gustaría profundizar algunos detalles aquí para que otros los encuentren también. Entré a detalles similares sobre PagedResourceAssembler en esta respuesta .

Modelos de representación

Spring HATEOAS se envía con una variedad de clases base para modelos de representación que facilitan la creación de representaciones equipadas con enlaces. Hay tres tipos de clases proporcionadas de manera inmediata:

  • Resource : un recurso de elemento. Efectivamente para envolver algún DTO o entidad que captura un solo elemento y lo enriquece con enlaces.
  • Resources : un recurso de recopilación, que puede ser una colección de cosas, pero generalmente son una colección de instancias de Resource .
  • PagedResources : una extensión de los Resources que captura información de paginación adicional como el número total de páginas, etc.

Todas estas clases se derivan de ResourceSupport , que es un contenedor básico para las instancias de Link .

Montadores de recursos

Un ResourceAssembler es ahora el componente atenuante para convertir sus objetos de dominio o DTO en dichas instancias de recursos. La parte importante aquí es que convierte un objeto fuente en un objeto objetivo.

Por lo tanto, PagedResourcesAssembler tomará una instancia de Spring Data Page y la transformará en una instancia de PagedResources al evaluar la Page y crear los PageMetadata necesarios, así como los enlaces PageMetadata y next para navegar por las páginas. De forma predeterminada, y esta es probablemente la parte más interesante aquí, usará SimplePagedResourceAssembler (una clase interna de PRA ) para transformar los elementos individuales de la página en instancias de Resource anidados.

Para permitir personalizar esto, PRA tiene métodos de toResource(…) adicionales toResource(…) que toman un delegado ResourceAssembler para procesar los elementos individuales. Entonces terminas con algo como esto:

class UserResource extends ResourceSupport { … } class UserResourceAssembler extends ResourceAssemblerSupport<User, UserResource> { … }

Y el código del cliente ahora se ve así:

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);

panorama

A partir del próximo Spring Data Commons 1.7 RC1 (y Spring HATEOAS 0.9 transitivamente), los enlaces prev y next se generarán como plantillas de URI compatibles con RFC6540 para exponer los parámetros de solicitud de paginación configurados en HandlerMethodArgumentResolvers para Pageable y Sort .

La configuración que ha mostrado arriba se puede simplificar anotando la clase de configuración con @EnableSpringDataWebSupport que le permitiría deshacerse de todas las declaraciones de @EnableSpringDataWebSupport explícitas.

Estoy usando Spring 4.0.0.RELEASE, Spring Data Commons 1.7.0.M1, Spring Hateoas 0.8.0.RELEASE

Mi recurso es un POJO simple:

public class UserResource extends ResourceSupport { ... }

Mi ensamblador de recursos convierte los objetos de usuario en objetos 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 } }

Dentro de mi UserController, quiero recuperar la Page<User> de mi servicio y luego convertirla a PagedResources<UserResource> usando PagedResourcesAssembler , como se muestra aquí: https://stackoverflow.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); }

Esto no llama a UserResourceAssembler y simplemente se devuelve el contenido del User lugar de mi UserResource personalizado.

Devolver un solo recurso funciona:

@Autowired UserResourceAssembler assembler; @RequestMapping(value="{id}", method=RequestMethod.GET) UserResource getById(@PathVariable ObjectId id) throws NotFoundException { return assembler.toResource(service.getById(id)); }

El PagedResourcesAssembler quiere un argumento genérico, pero luego no puedo usar T toResource(T) , porque no quiero convertir mi Page<User> a PagedResources<User> , especialmente porque User es un POJO y no hay recursos.

Entonces la pregunta es: ¿Cómo funciona?

EDITAR:

Mi 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); } /* ... */ }

SOLUCIÓN:

@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); }


FORMA ALTERNATIVA

Otra forma es usar el encabezado HTTP de rango (leer más en RFC 7233 ). Puede definir el encabezado HTTP de esta manera:

Range: resources=20-41

Eso significa que desea obtener recursos del 20 al 41 (incluido). De esta forma, los consumos de API reciben recursos exactamente definidos.

Es solo una forma alternativa. El rango se usa a menudo con otras unidades (como bytes, etc.)

MANERA RECOMENDADA

Si quieres trabajar con paginación y tener una API realmente aplicable (hipermedia / HATEOAS incluida), entonces te recomiendo añadir página y tamaño de página a tu URL. Ejemplo:

http://host.loc/articles?Page=1&PageSize=20

Luego, puede leer estos datos en su BaseApiController y crear algún objeto QueryFilter en todas sus solicitudes:

{ 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; }

Su API debe devolver alguna colección especial con información sobre la cantidad de elementos.

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; } }

Sus clases modelo pueden heredar alguna clase con soporte de paginación:

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; } }