Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to map extended DTO's from same source class

Tags:

java

mapstruct

I've started using Mapstruct to map JPA entities to DTO's. For basic entities, this works great.

My problem: Some entities have lazy loaded collections, containing additional details, that I don't want always want to fetch and map. As a solution I've added a basic superclass with all fields that are always mapped, and a subclass containing the collections. They both represent the same entity, so they use the same source class.

When I try to create a Mapper containing methods to map to both types from the same source, I get an ambiguous mapping methods error, even though the method signature (at least the return type) is different. Am I going about this the wrong way? Can't I use subclasses for DTO's using the same source?

Edit: In case it matters, I'm using mapstruct-jdk8:1.1.0.Final

Edit 2: The example below was just an example, on the top of my head. When I've actually used the code, it worked. It turns out my issue is with something that wasn't included in the example. It appears that the error occurs when I add a method for mapping a Collection of Tickets. This probably means the issue is not (directly?) related with inheritance. I'm probably missing some configuration, but I'm not sure what to look for.

Simple example:

Ticket entity

public class Ticket {
  private long id;
  private String title;
  private Set<Comment> comments;

  // Getters and setters
}

Ticket DTO

public class TicketDTO {
  private long id;
  private String title;

  // Getters and setters
}

Ticket with comments DTO

public class TicketWithCommentsDTO extends TicketDTO {
  private List<CommentDTO> comments;


  // Getters and setters
}

Ticket Mapper interface

@Mapper(uses= { CommentMapper.class })
public interface TicketMapper {
  TicketDTO mapToTicketDTO(Ticket ticket);

  List<TicketDTO> mapToTicketDTOList(Collection<Ticket> tickets); // Adding this method or the last method causes the error

  TicketWithCommentsDTO mapToTicketWithCommentsDTO(Ticket ticket);

  List<TicketWithCommentsDTO> MapToTicketWithCommentDTOList(Collection<Ticket> tickets); 
}

Comment Mapper interface

@Mapper
public interface CommentMapper {
  CommentDTO toCommentDTO(Comment comment);

  List<CommentDTO> toCommentDTOList(Collection<Comment> comments);
}

The error thrown:

 Ambiguous mapping methods found for mapping collection element to 
 dto.TicketDTO: dto.TicketDTO mapToTicketDTO(model.Ticket ticket), 
 dto.TicketWithCommentsDTO mapToTicketWithCommentsDTO(model.Ticket ticket).
like image 676
Steen Avatar asked Feb 01 '17 12:02

Steen


People also ask

How do you ignore property in MapStruct?

We can ignore unmapped properties in several mappers by setting the unmappedTargetPolicy via @MapperConfig to share a setting across several mappers. We should note that this is a simple example showing the minimal usage of @MapperConfig, which might not seem much better than setting the policy on each mapper.

How do I set default value in MapStruct?

Java Prime Pack Using Mapstruct we can pass the default value in case source property is null using defaultValue attribute of @Mapping annotation.

Why should we use MapStruct?

MapStruct is a code generator that greatly simplifies the implementation of mappings between Java bean types based on a convention over configuration approach. The generated mapping code uses plain method invocations and thus is fast, type-safe and easy to understand.


1 Answers

Well, this turned out to be a simple fix, it was indeed a missing configuration issue. What was missing was the @IterableMapping annotation.

Once I set the elementTargetType to the correct types, everything worked as expected.

The correct Mapper code

@Mapper(uses = { CommentMapper.class })
public interface TicketMapper {
    TicketDTO mapToTicketDTO(Ticket ticket);

    @IterableMapping(elementTargetType = TicketDTO.class)
    List<TicketDTO> mapToTicketDTOList(Collection<Ticket> tickets);

    TicketWithCommentsDTO mapToTicketWithCommentsDTO(Ticket ticket);

    @IterableMapping(elementTargetType = TicketWithCommentsDTO.class)
    List<TicketWithCommentsDTO> mapToTicketWithCommentDTOList(Collection<Ticket> tickets);
}
like image 163
Steen Avatar answered Oct 23 '22 13:10

Steen