81

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?

Tuomas Toivonen
  • 21,690
  • 47
  • 129
  • 225
  • 4
    I'm not sure but I guess you can use `Page.map` without lambda expressions. Just pass an instance of `Converter super T, ? extends S>` – Ali Dehghani Aug 19 '16 at 10:45

13 Answers13

97

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;
    }
});
Ali Dehghani
  • 46,221
  • 15
  • 164
  • 151
32

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;
    }
});
JayL
  • 2,819
  • 4
  • 22
  • 15
31

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.

Mustafa
  • 5,624
  • 3
  • 24
  • 40
  • 2
    To use a DTO constructor like the OP requested, you can use .map(ObjectDto::new); instead of .map(ObjectDto::fromEntity); (this assumes you have the proper constructor defined in ObjectDto) – java-addict301 Mar 14 '19 at 16:01
11

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;
}
Ilias Mentz
  • 500
  • 1
  • 7
  • 16
6

I have created a solution with model mapper, generics and lambdas for common usage, and it is used on a daily basis on several projects.

/**
 * 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 a 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);
Bosko Mijin
  • 3,287
  • 3
  • 32
  • 45
4

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);
            }

        });
    }
Tuomas Toivonen
  • 21,690
  • 47
  • 129
  • 225
3

use lambda expression is more convenient

Page<ObjectDto> dto=objectRepository.findAll(pageable).map((object -> DozerBeanMapperBuilder.buildDefault().map(object, ObjectDto.class)));
Evol Rof
  • 2,528
  • 2
  • 22
  • 37
3
Page<Order> persistedOrderPage = orderQueryRepository.search();

Page<OrderDTO> orderPage = persistedOrderPage.map(persistedOrder -> {
    OrderDTO order = mapper.toOrderDTO(persistedOrder);
    // do another action
    return order;
});
Oguzhan Cevik
  • 638
  • 8
  • 18
2

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.

MartenCatcher
  • 2,713
  • 8
  • 26
  • 39
2

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;
}
Vikash Kumar
  • 1,096
  • 11
  • 10
2

I've used Lambda with ModelMapper

    Page<ObjectEntity> pageEntity = objectRepository.findAll(pageable);
    Page<ObjectDto> pageDto = pageEntity.map(objectEntity -> modelMapper.map(objectEntity, ObjectDto.class));
ahmet aslan
  • 101
  • 1
  • 2
0

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.

Community
  • 1
  • 1
Peter Mutisya
  • 350
  • 4
  • 7
0

Try this code

SERVICE

Pageable pageable = PageRequest.of(page - 1, size);
Page<ContentResDTO> resDTOPage = contentRepository.findAllBy(contentID, userId, pageable);

REPOSITORY

@Query("SELECT new com.example.demo.dto.content.ContentResDTO(t, COUNT(DISTINCT c.cmtID), COUNT(DISTINCT r.rctId)) " +
            "FROM TMnTrContent t LEFT JOIN TMnTrComment c ON t.cntID = c.content.cntID LEFT JOIN TMnTrReact r ON t.cntID = r.content.cntID " +
            " WHERE (:cntId IS NULL OR (:cntId IS NOT NULL AND t.cntID = :cntId)) AND " +
            " (:userId IS NULL OR (:userId IS NOT NULL AND t.user.userId = :userId)) " +
            " GROUP BY t.cntID")
    Page<ContentResDTO> findAllBy(@Param("cntId") Long cntId,
                                  @Param("userId") Long userId,
                                  Pageable pageable);

DTO

@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
public class ContentResDTO {
    private long id;
    private long userId;
    private String type;
    private String description;
    private int version;
    private Date createdDate;
    private Date modifyDate;
    private int commentCount;
    private int reactCount;

    public ContentResDTO(TMnTrContent content, long commentCount, long reactCount) {
        this.id = content.getCntID();
        this.userId = content.getUser().getUserId();
        this.type = content.getCntType();
        this.description = content.getCntDescription();
        this.version = content.getVersion();
        this.createdDate = content.getCreateDate();
        this.modifyDate = content.getModifyDate();
        this.commentCount = (int) commentCount;
        this.reactCount = (int) reactCount;
    }

}