How to correctly use PagedResourcesAssembler from Spring Data?
You seem to have already found out about the proper way to use but I'd like to go into some of the details here a bit for others to find as well. I went into similar detail about PagedResourceAssembler
in this answer.
Representation models
Spring HATEOAS ships with a variety of base classes for representation models that make it easy to create representations equipped with links. There are three types of classes provided out of the box:
-
Resource
- an item resource. Effectively to wrap around some DTO or entity that captures a single item and enriches it with links. -
Resources
- a collection resource, that can be a collection of somethings but usually are a collection ofResource
instances. -
PagedResources
- an extension ofResources
that captures additional pagination information like the number of total pages etc.
All of these classes derive from ResourceSupport
, which is a basic container for Link
instances.
Resource assemblers
A ResourceAssembler
is now the mitigating component to convert your domain objects or DTOs into such resource instances. The important part here is, that it turns one source object into one target object.
So the PagedResourcesAssembler
will take a Spring Data Page
instance and transform it into a PagedResources
instance by evaluating the Page
and creating the necessary PageMetadata
as well as the prev
and next
links to navigate the pages. By default - and this is probably the interesting part here - it will use a plain SimplePagedResourceAssembler
(an inner class of PRA
) to transform the individual elements of the page into nested Resource
instances.
To allow to customize this, PRA
has additional toResource(…)
methods that take a delegate ResourceAssembler
to process the individual items. So you end up with something like this:
class UserResource extends ResourceSupport { … }
class UserResourceAssembler extends ResourceAssemblerSupport<User, UserResource> { … }
And the client code now looking something like this:
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);
Outlook
As of the upcoming Spring Data Commons 1.7 RC1 (and Spring HATEOAS 0.9 transitively) the prev
and next
links will be generated as RFC6540 compliant URI templates to expose the pagination request parameters configured in the HandlerMethodArgumentResolvers
for Pageable
and Sort
.
The configuration you've shown above can be simplified by annotating the config class with @EnableSpringDataWebSupport
which would let you get rid off all the explicit bean declarations.
Related videos on Youtube
Benjamin M
Updated on March 09, 2020Comments
-
Benjamin M about 4 years
I'm using Spring 4.0.0.RELEASE, Spring Data Commons 1.7.0.M1, Spring Hateoas 0.8.0.RELEASE
My resource is a simple POJO:
public class UserResource extends ResourceSupport { ... }
My resource assembler converts User objects to UserResource objects:
@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 } }
Inside my UserController I want to retrieve
Page<User>
from my service and then convert it toPagedResources<UserResource>
usingPagedResourcesAssembler
, like displayed here: 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); }
This doesn't call
UserResourceAssembler
and simply the contents ofUser
are returned instead of my customUserResource
.Returning a single resource works:
@Autowired UserResourceAssembler assembler; @RequestMapping(value="{id}", method=RequestMethod.GET) UserResource getById(@PathVariable ObjectId id) throws NotFoundException { return assembler.toResource(service.getById(id)); }
The
PagedResourcesAssembler
wants some generic argument, but then I can't useT toResource(T)
, because I don't want to convert myPage<User>
toPagedResources<User>
, especially becauseUser
is a POJO and no Resource.So the question is: How does it work?
EDIT:
My 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); } /* ... */ }
SOLUTION:
@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); }
-
Benjamin M over 10 yearsHi, could you please tell how to use those generics right with
PagedResourcesAssembler
? I only can use it as raw type. It saysPagedResources<R> toResource(Page<T>, ResourceAssemblerSupport<T, R>)
. Eclipse won't accept this:PagedResources<UserResource> p = parAssembler.toResource(userPage, userResourceAssembler)
. -
norgence over 10 yearsI've added the generics for the
PagedResourcesAssembler
in the answer above. If you provide a customUserResourceAssembler
which is aResourceAssembler<User, UserResource>
, you need to get aPagedResourcesAssembler<User>
injected to eventually createPagedResources<UserResource>
. I just added a test case to the codebase giving a working example of that. -
Stephane almost 10 yearsFacing the same issue, on Spring 4.0.6.RELEASE and 0.16.0.RELEASE the toResource accepts only one argument, the adminResourcePages, and no way to pass in the custom adminResourceAssembler. Here is the method signature: toResource(Page<AdminResource>, Link) Am I using the wrong API here ?
-
Stephane almost 10 yearsI've got it now: Page<EventAdmin> eventAdminPages = new PageImpl<EventAdmin>(searchedAdminsEvent.getEventAdmins(), pageable, searchedAdminsEvent.getTotalElements()); PagedResources<AdminResource> adminPageResources = pagedResourcesAssembler.toResource(eventAdminPages, adminResourceAssembler);
-
Stephane almost 10 yearsIn this new way of paging the resources, is there any use left for overriding toResources ? (Note the trailing 's'). Cheers.
-
Stephane over 9 yearsAny way to have the links of a sub resource ? Inside my main resource, the admin resource does not have its links, since the toResource method of the main resource assembler only provides the links for the main resource.
-
Stephane over 9 yearsI posted a question to be clearer. stackoverflow.com/questions/25602402/…
-
Benjamin M over 9 yearsYou answer has got nothing to do with Springs
PagedResourcesAssembler
. Model classes should never inherit any API stuff....
RegardingRange
: Not recommendable at all! The generated URL is not idempotent, but GET requests should be. This means, I can call the same URL with differentRange
headers and get different results. --- You can't copy and paste the URL and get the same result, because you will always loose theRange
information. -
Miroslav Holec over 9 years1) agree, I just showed alternative way... Range is commonly used for streaming api's
-
Miroslav Holec over 9 years2) agree and my example doesn't contain any data model classes, there are just API model classes
-
PAA over 8 yearsCould you please show us your toResource method? Regards, Neha
-
Robert Bain about 8 yearsAm I correct in my assertion that
PagedResourcesAssembler
takes a JPA entity and the@Controller
method signature takes aPagedResourcesAssembler<JPAEntity>
? When I'm thinking about my implementation, this feels like a leaky abstraction. I feel that the abstraction would be more cohesive ifPagedResourcesAssembler
took a<Resource of JPAEntity>
. That way our persistence layer deals with JPA entities and our service layer maps those to<? extends ResourceSupport>
. I generally don't expect JPA entities to be referenced above the service layer. Am I using the API incorrectly? -
Rafael about 7 years@OliverGierke is there a ResourcesAssembler to handle collection of resourced that are not needed to be paged?
-
Jefferson Lima over 5 yearsWhen using Spring Data Rest, I don't need to create a
Resource
nor aResourceAssembler
class, this leads me to think that SDR might have a generic implementation of these classes. Is there a way to use it?