How to map Page<ObjectEntity> to Page<ObjectDTO> in spring-data-rest

JavaSpringSpring MvcSpring DataSpring Data-Rest

Java 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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTuomas ToivonenView Question on Stackoverflow
Solution 1 - JavaAli DehghaniView Answer on Stackoverflow
Solution 2 - JavaJayLView Answer on Stackoverflow
Solution 3 - JavaMustafaView Answer on Stackoverflow
Solution 4 - JavaIlias MentzView Answer on Stackoverflow
Solution 5 - JavaTuomas ToivonenView Answer on Stackoverflow
Solution 6 - JavaBosko MijinView Answer on Stackoverflow
Solution 7 - JavaEvol RofView Answer on Stackoverflow
Solution 8 - JavaBiswajit DuttaView Answer on Stackoverflow
Solution 9 - JavaVikash KumarView Answer on Stackoverflow
Solution 10 - JavaOguzhan CevikView Answer on Stackoverflow
Solution 11 - JavaPeter MutisyaView Answer on Stackoverflow