11

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).
Steen
  • 311
  • 1
  • 3
  • 11
  • Can you maybe show us the mappers that are throwing the ambiguous method error? It might be more clear where the problem is exactly (a bug or something else) – Filip Feb 01 '17 at 18:49
  • @Filip, thanks for replying! I've edited my question, could you please have a look? Let me know if you need anything else. – Steen Feb 02 '17 at 08:28

2 Answers2

7

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);
}
Steen
  • 311
  • 1
  • 3
  • 11
  • it looks like you have found the answer. I would just like to clarify one small detail. The `elementTargetType` is not needed on the `mapToTicketWithCommentDTOList`. The reason for this is that there exists only on method that can map a `Ticket` into a `TicketWithCommentsDTO` (if your examples are complete). On the other side, there are 2 possible methods that can map a `Ticket` into a `TicketDTO` (`mapToTicketDTO` and `mapToTicketWithCommentsDTO`), the ambiguous error message comes from here. – Filip Feb 02 '17 at 17:32
  • @Filip That makes sense (quite embarrassing that I didn't catch that, really). Thanks for the clarification! – Steen Feb 03 '17 at 07:49
  • I wonder if there is any solution to this if I'm not using IterableMapping. – ch1ll Jul 11 '23 at 08:27
0

You can do subclass mapping by adding some after mapping methods like the following:

@Mapper(...)
public interface SuperClassMapper {

SubClassMapper SUBCLASS_MAPPER_INSTANCE = Mappers.getMapper(SubClassMapper.class);

// Mappings entity to dto / dto to entity ...

@AfterMapping
default SuperClassDTO toSubClassDTO(SuperClass entity, @MappingTarget SuperClassDTO dto) 
{
    if (entity instanceof SubClass) {
        return SUBCLASS_MAPPER_INSTANCE.toDto((SubClass) entity);
    }
    return dto;
}

You should have of course a SubClassMapper already defined.

Soufiane KAMAD
  • 139
  • 1
  • 6