Learn to build automatic pagination links in Spring Hateaos application using PagedModel and PagedResourcesAssembler classes.
1. JPA PagingAndSortingRepository
First thing, we need to use PagingAndSortingRepository
repository which provides methods to retrieve entities using the pagination and sorting abstraction.
It is important because we do not want to rewrite the JPA queries to read data in a paging fashion, as it is available just by implementing this simple interface.
import org.springframework.data.repository.PagingAndSortingRepository;
import com.howtodoinjava.rest.entity.AlbumEntity;
public interface AlbumRepository extends PagingAndSortingRepository<AlbumEntity, Long>{
}
2. Pagination with PagedModel using PagedResourcesAssembler
- To enable automatic pagination links, we must use
PagedModel
provided by spring hateoas module which helps in creating representations of pageable collections. PagedResourcesAssembler
accepts the JPA entities list, and converts it toPagedModel
.- Additionally, we can use
RepresentationModelAssembler
to convert JPA entities intoCollectionModel
having custom resource representation. - Finally,
PagedModel
is returned as the API response from the REST controller.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.PagedModel;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.howtodoinjava.rest.assembers.AlbumModelAssembler;
import com.howtodoinjava.rest.entity.AlbumEntity;
import com.howtodoinjava.rest.model.AlbumModel;
import com.howtodoinjava.rest.repository.AlbumRepository;
@RestController
public class WebController
{
@Autowired
private AlbumRepository albumRepository;
@Autowired
private AlbumModelAssembler albumModelAssembler;
@Autowired
private PagedResourcesAssembler<AlbumEntity> pagedResourcesAssembler;
@GetMapping("/api/albums-list")
public ResponseEntity<PagedModel<AlbumModel>> getAllAlbums(Pageable pageable)
{
Page<AlbumEntity> albumEntities = albumRepository.findAll(pageable);
PagedModel<AlbumModel> collModel = pagedResourcesAssembler
.toModel(albumEntities, albumModelAssembler);
return new ResponseEntity<>(collModel,HttpStatus.OK);
}
}
The AlbumModelAssembler converts a JPA entity to a DTO object (entity and collection representations). i.e. it converts AlbumEntity
to AlbumModel
.
@Component
public class AlbumModelAssembler
extends RepresentationModelAssemblerSupport<AlbumEntity, AlbumModel> {
public AlbumModelAssembler() {
super(WebController.class, AlbumModel.class);
}
@Override
public AlbumModel toModel(AlbumEntity entity)
{
AlbumModel albumModel = instantiateModel(entity);
albumModel.add(linkTo(
methodOn(WebController.class)
.getActorById(entity.getId()))
.withSelfRel());
albumModel.setId(entity.getId());
albumModel.setTitle(entity.getTitle());
albumModel.setDescription(entity.getDescription());
albumModel.setReleaseDate(entity.getReleaseDate());
albumModel.setActors(toActorModel(entity.getActors()));
return albumModel;
}
@Override
public CollectionModel<AlbumModel> toCollectionModel(Iterable<? extends AlbumEntity> entities)
{
CollectionModel<AlbumModel> actorModels = super.toCollectionModel(entities);
actorModels.add(linkTo(methodOn(WebController.class).getAllAlbums()).withSelfRel());
return actorModels;
}
private List<ActorModel> toActorModel(List<ActorEntity> actors) {
if (actors.isEmpty())
return Collections.emptyList();
return actors.stream()
.map(actor -> ActorModel.builder()
.id(actor.getId())
.firstName(actor.getFirstName())
.lastName(actor.getLastName())
.build()
.add(linkTo(
methodOn(WebController.class)
.getActorById(actor.getId()))
.withSelfRel()))
.collect(Collectors.toList());
}
}
3. Verify Pagination Links
Run the application and invoke REST API with paging request parameters.
Page numbers start with zero (0).
http://localhost:8080/api/albums-list?page=1&size=2&sort=title,desc
{
"_links": {
"first": {
"href": "http://localhost:8080/api/albums-list?page=0&size=2&sort=title,desc"
},
"prev": {
"href": "http://localhost:8080/api/albums-list?page=0&size=2&sort=title,desc"
},
"self": {
"href": "http://localhost:8080/api/albums-list?page=1&size=2&sort=title,desc"
},
"next": {
"href": "http://localhost:8080/api/albums-list?page=2&size=2&sort=title,desc"
},
"last": {
"href": "http://localhost:8080/api/albums-list?page=4&size=2&sort=title,desc"
}
},
"page": {
"size": 2,
"totalElements": 10,
"totalPages": 5,
"number": 1
},
"_embedded": {
"albums": [
{
"id": 7,
"title": "Top Hits Vol 7",
"description": "Top hits vol 7. description",
"releaseDate": "10-03-1987",
"actors": [
{
"id": 4,
"firstName": "Janice",
"lastName": "Preston",
"_links": {
"self": {
"href": "http://localhost:8080/api/actors/4"
}
}
}
],
"_links": {
"self": {
"href": "http://localhost:8080/api/actors/7"
}
}
},
{
"id": 6,
"title": "Top Hits Vol 6",
"description": "Top hits vol 6. description",
"releaseDate": "10-03-1986",
"actors": [
{
"id": 3,
"firstName": "Laverne",
"lastName": "Mann",
"_links": {
"self": {
"href": "http://localhost:8080/api/actors/3"
}
}
}
],
"_links": {
"self": {
"href": "http://localhost:8080/api/actors/6"
}
}
}
]
}
}
We can verify different pagination and sorting options in URL using query parameters and they will work.
Happy Learning !!