Como mapear classes estendidas no MapStruct

Aug 23 2020

Tenho uma pergunta sobre mapStruct. Tenho um caso em que estendo a classe da entidade base e não tenho certeza de como mapeá-la. Aqui está o meu caso.

BaseEntidade:

public class BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "id")
    private Long id;
}

BaseDto:

public class BaseDto {

    private Long id;
}

UserEntity:

public class User extends BaseEntity {
    private String name;
    private String lastName;
    private String username;
    private String password;
    private String profilePicturePath;
}

UsuárioDto:

public class UserDto extends BaseDto {
    private String name;
    private String lastName;
    private String username;
    private String password;
    private String profilePicturePath;
}

E o mapper é assim:

@Mapper(uses = {BaseMapper.class})
public interface UserMapper {

    User userDtoToUser(UserDto userDto);

    UserDto userToUserDto(User user);
}

Base Mapper:

@Mapper
public interface BaseMapper {

    BaseEntity dtoToEntity(BaseDto baseDto);

    BaseDto entityToDto(BaseEntity baseEntity);
}

O problema é que não recebo a propriedade de ID mapeada.

Obrigado pelo seu tempo.

EDITAR:

Não há erro mostrado, na implementação do mapeador (código gerado) não há mapeamento para esse ID:

 @Override
    public User userDtoToUser(UserDto userDto) {
        if ( userDto == null ) {
            return null;
        }

        UserBuilder user = User.builder();

        user.name( userDto.getName() );
        user.lastName( userDto.getLastName() );
        user.username( userDto.getUsername() );
        user.password( userDto.getPassword() );
        user.profilePicturePath( userDto.getProfilePicturePath() );

        return user.build();
    }

Respostas

2 Pankaj Aug 23 2020 at 19:51

Eu estou supondo (como você não colocou buidero código) o problema é que sua classe de construtor não inclui o campo de classe pai. O MapStruct faz algumas suposições ao gerar o código para o mapeador. Da documentação -

A implementação padrão do BuilderProvider assume o seguinte:

  1. O tipo tem um método de criação de construtor estático público sem parâmetros que retorna um construtor. Por exemplo, Person tem um método estático público que retorna PersonBuilder.
  2. O tipo construtor tem um método público sem parâmetros (método build) que retorna o tipo que está sendo construído. Em nosso exemplo, PersonBuilder tem um método que retorna Person.
  3. Caso existam vários métodos de compilação, o MapStruct procurará um método chamado build, se tal método existir, ele será usado, caso contrário, um erro de compilação será gerado.

Se você estiver usando o Lombok, poderá resolver isso usando @SuperBuildercomo -

@SuperBuilder
@Getter
@ToString
public class UserDto extends BaseDto {
  private String name;
  private String lastName;
  private String username;
  private String password;
  private String profilePicturePath;
}

@Getter
@SuperBuilder
class BaseDto {
  private Long id;
}

@SuperBuilder
@Getter
@ToString
public class User extends BaseEntity {
  private String name;
  private String lastName;
  private String username;
  private String password;
  private String profilePicturePath;
}

@Setter
@Getter
@SuperBuilder
class BaseEntity {
  private Long id;
}

E gerado pode parecer -

@Override
public User userDtoToUser(UserDto userDto) {
    if ( userDto == null ) {
        return null;
    }

    UserBuilder<?, ?> user = User.builder();

    user.id( userDto.getId() );
    user.name( userDto.getName() );
    user.lastName( userDto.getLastName() );
    user.username( userDto.getUsername() );
    user.password( userDto.getPassword() );
    user.profilePicturePath( userDto.getProfilePicturePath() );

    return user.build();
}