In a project that uses Spring Data JPA, you can implement pagination with the EntityManager
quite efficiently, because it allows you to directly map the result set values into DTOs, skipping redundant entity to DTO mapping:
@Service
public class ItemService {
@PersistenceContext
private EntityManager entityManager;
@Transactional(readOnly = true)
public Page<ItemDTO> getItemsPage(Pageable page) {
var itemList = entityManager
.unwrap(Session.class)
.createNamedQuery("Item.availableItems", Object[].class)
.setFirstResult(page.getPageNumber() * page.getPageSize())
.setMaxResults(page.getPageSize())
.setTupleTransformer((tuples, aliases) -> {
var item = new ItemDTO();
item.setId((Long) tuples[0]);
item.setName((String) tuples[1]);
//...
return item;
})
.getResultList();
long totalCount = itemList.size() < page.getPageSize()
? page.getPageSize()
: entityManager
.createQuery("SELECT count(i) FROM Item i", Long.class)
.getSingleResult();
return new PageImpl<>(itemList, page, totalCount);
}
}
In this example, the query is defined in the Item
entity class:
@NamedQuery(
name = "Items.availableItems",
query = "SELECT i.id, i.name FROM Item i"
)
@Entity
@Table(name = "items")
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// ...
}
With this you can easily return the paginated result:
public ResponseEntity<List<ItemDTO>> getAvailableItems(
@Min(0) @Valid @RequestParam(value = "pageNumber", defaultValue = "0") Integer pageNumber,
@Min(1) @Valid @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
Page<ItemDTO> items = itemService.getAvailableItemsPage(PageRequest.of(pageNumber, pageSize));
return ResponseEntity
.ok()
.header("x-total-count", String.valueOf(items.getTotalElements()))
.header("x-page-number", String.valueOf(pageNumber))
.header("x-page-size", String.valueOf(pageSize))
.body(items.toList());
}