How to map Page<ObjectEntity> to Page<ObjectDTO> in spring-data-rest
JavaSpringSpring MvcSpring DataSpring Data-RestJava Problem Overview
When I hit the database with PagingAndSortingRepository.findAll(Pageable)
I get Page<ObjectEntity>
. However, I want to expose DTO's to the client and not entities. I can create DTO just by injecting entity into it's constructor, but how do I map the entities in Page object to DTO's? According to spring documentation, Page provides read-only operations.
Also, Page.map is not possibility, as we don't have support for java 8. How to create the new Page with mapped objects manually?
Java Solutions
Solution 1 - Java
You can still use the Page.map
without lambda expressions:
Page<ObjectEntity> entities = objectEntityRepository.findAll(pageable);
Page<ObjectDto> dtoPage = entities.map(new Converter<ObjectEntity, ObjectDto>() {
@Override
public ObjectDto convert(ObjectEntity entity) {
ObjectDto dto = new ObjectDto();
// Conversion logic
return dto;
}
});
Solution 2 - Java
In Spring Data 2, the Page map method takes a Function instead of a Converter, but it still works basically the same as @Ali Dehghani described.
Using Function:
Page<ObjectEntity> entities = objectEntityRepository.findAll(pageable);
Page<ObjectDto> dtoPage = entities.map(new Function<ObjectEntity, ObjectDto>() {
@Override
public ObjectDto apply(ObjectEntity entity) {
ObjectDto dto = new ObjectDto();
// Conversion logic
return dto;
}
});
Solution 3 - Java
And in java8:
Page<ObjectDto> entities =
objectEntityRepository.findAll(pageable)
.map(ObjectDto::fromEntity);
Where fromEntity is a static method on ObjectDto that contains the conversion logic.
Solution 4 - Java
You can use Page.map by simply doing this:
public Page<ObjectDto> toPageObjectDto(Page<Object> objects) {
Page<ObjectDto> dtos = objects.map(this::convertToObjectDto);
return dtos;
}
private ObjectDto convertToObjectDto(Object o) {
ObjectDto dto = new ObjectDto();
//conversion here
return dto;
}
Solution 5 - Java
Here is my solution, thanks to @Ali Dehghani
private Page<ObjectDTO> mapEntityPageIntoDTOPage(Page<ObjectEntity> objectEntityPage) {
return objectEntityPage.map(new Converter<ObjectEntity, ObjectDTO>() {
public ObjectDTO convert(ObjectEntity objectEntity) {
return new ObjectDTO(objectEntity, httpSession);
}
});
}
Solution 6 - Java
I am created and using solution with model mapper, generics and lambdas for common usage.
/**
* Maps the Page {@code entities} of <code>T</code> type which have to be mapped as input to {@code dtoClass} Page
* of mapped object with <code>D</code> type.
*
* @param <D> - type of objects in result page
* @param <T> - type of entity in <code>entityPage</code>
* @param entities - page of entities that needs to be mapped
* @param dtoClass - class of result page element
* @return page - mapped page with objects of type <code>D</code>.
* @NB <code>dtoClass</code> must has NoArgsConstructor!
*/
public <D, T> Page<D> mapEntityPageIntoDtoPage(Page<T> entities, Class<D> dtoClass) {
return entities.map(objectEntity -> modelMapper.map(objectEntity, dtoClass));
}
This is exactly the case which you need (and I think common case for wide range of other cases).
You already have the data obtained from repository (same is with service) on this way:
Page<ObjectEntity> entities = objectEntityRepository.findAll(pageable);
Everything what you need for conversion is to call this method on this way:
Page<ObjectDto> dtoPage = mapEntityPageIntoDtoPage(entities, ObjectDto.class);
@Tip: You can use this method from util class, and it can be reused for all entity/dto in Page conversions on services and controllers according to your architecture.
Example:
Page<ObjectDto> dtoPage = mapperUtil.mapEntityPageIntoDtoPage(entities, ObjectDto.class);
Solution 7 - Java
use lambda expression is more convenient
Page<ObjectDto> dto=objectRepository.findAll(pageable).map((object -> DozerBeanMapperBuilder.buildDefault().map(object, ObjectDto.class)));
Solution 8 - Java
This works correctly in Spring 2.0 -
@Override
public Page<BookDto> getBooksByAuthor(String authorId, Pageable pageable) {
Page<BookEntity> bookEntity = iBookRepository.findByAuthorId(authorId, pageable);
return bookEntity.map(new Function<BookEntity, BookDto>() {
@Override
public BookDto apply(BookEntity t) {
return new ModelMapper().map(t, BookDto.class);
}
});
}
The converter is no longer supported in Page type for Spring 2.0. Also, the Function should be used from java.util.function.Function.
Solution 9 - Java
Using Java 8 Lambda ,It worked for me. The answer is already given above,I am just simplifying.
Page<EmployeeEntity> employeeEntityPage = employeeService.findEmployeeEntities();
Page<EmployeeDto> employeeDtoPage = employeeEntityPage.map(entity -> {
EmployeeDto dto = employeeService.employeEntityToDto(entity);
return dto;
});
Here employeeEntityToDto() is a method to convert Entities to Dtos
public EmployeeDto employeeEntityToDto(EmployeeEntity entity){
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setId(entity.getId());
employeeDto.setName(entity.getName());
return employeeDto;
}
Solution 10 - Java
Page<Order> persistedOrderPage = orderQueryRepository.search();
Page<OrderDTO> orderPage = persistedOrderPage.map(persistedOrder -> {
OrderDTO order = mapper.toOrderDTO(persistedOrder);
// do another action
return order;
});
Solution 11 - Java
At the end, you will not return the Page to the users, but a list of ObjectDTO, with the Page details at the header, so this would be my solution.
ObjectService
public Page<ObjectEntity> findAll (Pageable pageable){
//logic goes here.
Page<ObjectEntity> page = objectRepository.findAll(pageable);
return page;
}
ObjectResource / rest (the exposed endpoint)
@GetMapping
public ResponseEntity<List<ObjectDTO>> findAll (Pageable pageable){
Page<ObjectEntity> page = objectServiceService.findAll(pageable);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "your-endpoint-here");
return new ResponseEntity<>(objectMapper.toDto(page.getContent()), headers, HttpStatus.OK);
}
The reason for using this is so that you don't need to duplicate the page details for ObjectEntity and DTO. It is key to note that a page contains the following:
- page number
- pageSize
- numberOfElements
- content
The content is the list of objects returned, and is the only thing that needs to be mapped to DTO.