There's support for a scenario like this upcoming in Spring HATEOAS and Spring Data Commons. Spring HATEOAS comes with a PageMetadata
object that essentially contains the same data as a Page
but in a less enforcing manner, so that it can be more easily marshaled and unmarshaled.
Another aspect of the reason we implement this in combination with Spring HATEOAS and Spring Data commons is that there's little value in simply marshaling the page, it's content and the metadata but also want to generate the links to maybe existing next or previous pages, so that the client doesn't have to construct URIs to traverse these pages itself.
An example
Assume a domain class Person
:
class Person {
Long id;
String firstname, lastname;
}
as well as it's corresponding repository:
interface PersonRepository extends PagingAndSortingRepository<Person, Long> { }
You can now expose a Spring MVC controller as follows:
@Controller
class PersonController {
@Autowired PersonRepository repository;
@RequestMapping(value = "/persons", method = RequestMethod.GET)
HttpEntity<PagedResources<Person>> persons(Pageable pageable,
PagedResourcesAssembler assembler) {
Page<Person> persons = repository.findAll(pageable);
return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
}
}
There's probably quite a bit to explain here. Let's take it step by step:
- We have a Spring MVC controller getting the repository wired into it. This requires Spring Data being set up (either through
@Enable(Jpa|Mongo|Neo4j|Gemfire)Repositories
or the XML equivalents). The controller method is mapped to /persons
, which means it will accept all GET
requests to that method.
- The core type returned from the method is a
PagedResources
- a type from Spring HATEOAS that represents some content enriched with Links
plus a PageMetadata
.
When the method is invoked, Spring MVC will have to create instances for Pageable
and PagedResourcesAssembler
. To get this working you need to enable the Spring Data web support either through the @EnableSpringDataWebSupport
annotation about to be introduced in the upcoming milestone of Spring Data Commons or via standalone bean definitions (documented here).
The Pageable
will be populated with information from the request. The default configuration will turn ?page=0&size=10
into a Pageable
requesting the first page by a page size of 10.
The PageableResourcesAssembler
allows you to easily turn a Page
into a PagedResources
instances. It will not only add the page metadata to the response but also add the appropriate links to the representation based on what page you access and how your Pageable
resolution is configured.
A sample JavaConfig configuration to enable this for JPA would look like this:
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
@EnableJpaRepositories
class ApplicationConfig {
// declare infrastructure components like EntityManagerFactory etc. here
}
A sample request and response
Assume we have 30 Persons
in the database. You can now trigger a request GET http://localhost:8080/persons
and you'll see something similar to this:
{ "links" : [
{ "rel" : "next", "href" : "http://localhost:8080/persons?page=1&size=20 }
],
"content" : [
… // 20 Person instances rendered here
],
"pageMetadata" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}
Note that the assembler produced the correct URI and also picks up the default configuration present to resolve the parameters into a Pageable
for an upcoming request. This means, if you change that configuration, the links will automatically adhere to the change. By default the assembler points to the controller method it was invoked in but that can be customized by handing in a custom Link
to be used as base to build the pagination links to overloads of the PagedResourcesAssembler.toResource(…)
method.
Outlook
The PagedResourcesAssembler
bits will be available in the upcoming milestone release of the Spring Data Babbage release train. It's already available in the current snapshots. You can see a working example of this in my Spring RESTBucks sample application. Simply clone it, run mvn jetty:run
and curl http://localhost:8080/pages
.