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

14

90

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?

Mutilate answered 19/8, 2016 at 10:30 Comment(1)
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>Maquette
M
103

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;
    }
});
Maquette answered 19/8, 2016 at 10:56 Comment(7)
Wow, this was line saver with Dozer mapper and lambda.Mortify
Hi @Ali I'm not sure which Covnerter class should I import. Can you cleear this doubt please?Unearthly
@MartinLarizzate Apparently new Spring Data versions are using simple Java 8 Functions. So take a look at here github.com/spring-projects/spring-data-commons/blob/…Maquette
Thanks for helping Ali DehganiUnearthly
what is the converter package?Sepia
@Dhanushkasasanka Probably you're using Spring Data 2, for that see https://mcmap.net/q/235286/-how-to-map-page-lt-objectentity-gt-to-page-lt-objectdto-gt-in-spring-data-restMaquette
Clean solution -> https://mcmap.net/q/235286/-how-to-map-page-lt-objectentity-gt-to-page-lt-objectdto-gt-in-spring-data-restLekishalela
T
34

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;
    }
});
Tenpenny answered 16/2, 2018 at 17:7 Comment(1)
in here what is the Function Package?Sepia
G
33

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.

Grandmamma answered 11/1, 2018 at 10:53 Comment(1)
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)Daugavpils
D
12

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;
}
Dyspepsia answered 22/8, 2018 at 9:20 Comment(0)
M
7

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);
Megagamete answered 14/3, 2020 at 18:7 Comment(0)
M
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);
            }

        });
    }
Mutilate answered 19/8, 2016 at 10:57 Comment(0)
B
4

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;
}
Bulley answered 3/8, 2021 at 10:5 Comment(0)
E
3

use lambda expression is more convenient

Page<ObjectDto> dto=objectRepository.findAll(pageable).map((object -> DozerBeanMapperBuilder.buildDefault().map(object, ObjectDto.class)));
Ermaermanno answered 2/5, 2020 at 16:57 Comment(0)
L
3
Page<Order> persistedOrderPage = orderQueryRepository.search();

Page<OrderDTO> orderPage = persistedOrderPage.map(persistedOrder -> {
    OrderDTO order = mapper.toOrderDTO(persistedOrder);
    // do another action
    return order;
});
Lekishalela answered 26/3, 2022 at 18:39 Comment(0)
T
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.

Tore answered 24/5, 2020 at 4:47 Comment(0)
P
2

I've used Lambda with ModelMapper

    Page<ObjectEntity> pageEntity = objectRepository.findAll(pageable);
    Page<ObjectDto> pageDto = pageEntity.map(objectEntity -> modelMapper.map(objectEntity, ObjectDto.class));
Petrology answered 28/1, 2023 at 21:49 Comment(0)
B
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.

Brewis answered 27/3, 2018 at 13:39 Comment(0)
S
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;
    }

}
Sitter answered 30/4, 2023 at 12:25 Comment(0)
B
0

Here is option, if you also need to get totalElements, totalPages from origin

@Component
@RequiredArgsConstructor
public class CreditIssuedMapper {

public static CreditIssuedDto toCreditIssuedDto(CreditIssued creditIssued){

    CreditIssuedDto c = new CreditIssuedDto();

    c.setId(creditIssued.getId());

    c.setClientId(creditIssued.getClientData().getId());
    c.setClientName(creditIssued.getClientData().getClientName());
    c.setClientSurname(creditIssued.getClientData().getClientSurname());
    c.setClientMiddlename(creditIssued.getClientData().getClientMiddlename());

    c.setManagerId(creditIssued.getManagerData().getId());
    c.setManagerName(creditIssued.getManagerData().getManagerName());
    c.setManagerSurname(creditIssued.getManagerData().getManagerSurname());
    c.setManagerMiddlename(creditIssued.getManagerData().getManagerMiddlename());

    c.setCreditIssuedBody(creditIssued.getCreditBody());
    c.setCreditIssuedLeft(creditIssued.getCreditLeft());
    c.setCreditIssuedFine(creditIssued.getCreditFine());
    c.setCreditIssuedMonthlyPayment(creditIssued.getCreditMonthlyPayment());
    c.setCreditIssuedDate(creditIssued.getCreditIssuedDate());
    c.setCreditIssuedNextPayment(creditIssued.getCreditNextPayment());
    c.setCreditIssuedExpDate(creditIssued.getCreditExpDate());

    c.setCreditOfferId(creditIssued.getCreditOfferId().getId());
    c.setCreditOfferName(creditIssued.getCreditOfferId().getCreditName());
    c.setCreditOfferInterest(creditIssued.getCreditOfferId().getCreditInterest());
    c.setCreditOfferFine(creditIssued.getCreditOfferId().getCreditFine());
    c.setCreditOfferCurrency(creditIssued.getCreditOfferId().getCurrencyData().getCurrencyName());

    return c;

}

public Page<CreditIssuedDto> creditIssuedDto (Page<CreditIssued> creditIssued){

    List<CreditIssuedDto> creditIssuedDto = creditIssued
            .stream()
            .map(CreditIssuedMapper::toCreditIssuedDto)
            .collect(Collectors.toList());

    long totalElements = creditIssued.getTotalElements();
    int totalPages = creditIssued.getTotalPages();

    return new PageImpl<>(creditIssuedDto, PageRequest.of(creditIssued.getNumber(), creditIssued.getSize()), totalElements);
}
}
Belgium answered 24/9, 2023 at 14:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.