Compreendendo e Utilizando o MapStruct em Java

O Que é o MapStruct?

MapStruct é um processador de anotações para Java que gera automaticamente implementações de código para o mapeamento de objetos (bean mapping) em tempo de compilação. Como o código é gerado durante a compilação, semelhante a um compilador, o desempenho é superior a soluções baseadas em reflexão.

Seu uso é particularmente valioso em arquiteturas de microsserviços ou aplicações com múltiplas camadas, onde os objetos de domínio (entidades) precisam ser convertidos para Data Transfer Objects (DTOs) ou View Models. O MapStruct simplifica drasticamente a implementação manual desses mapeamentos.

Características Principais

  • Segurança de Tipo: Erros de tipagem entre propriedades de origem e destino são detectados em tempo de compilação.
  • Alto Desempenho: Gera código Java puro, sem uso de reflexão em tempo de execução.
  • Flexibilidade: Suporta mapeamentos complexos, incluindo objetos aninhados, coleções, e conversões personalizadas.

Comparação com BeanUtils

Uma alternativa comum é a classe BeanUtils.copyProperties() do Spring Framwork. A principal diferença reside na abordagem e nas limitações.

Para a demonstração, considere as seguintes classes de exemplo:

// Classe de Origem (Source)
public class Employee {
    private String name;
    private String ageAsString;
    private String genderCode;
    private Address homeAddress;
    private String taxId;
    private LocalDate hireDate;
    private List<String> skills;
}

// Classe de Destino (Target)
public class EmployeeDTO {
    private String nome;
    private Integer idade;
    private Character genero;
    private Address endereco;
    private Long cpf;
    private String dataAdmissaoFormatada;
    private List<String> competencias;
}

// Classe auxiliar
public class Address {
    private String state;
    private String city;
    private String neighborhood;
}

Abordagem do BeanUtils

O BeanUtils opera por reflexão em tempo de execução. Ele copia valores de campos com o mesmo nome e tipo compatível. Campos com nomes diferentes, tipos incompatíveis ou campos estáticos são ignorados silenciosamente.

@Slf4j
public class BeanUtilsDemo {
    public static void main(String[] args) {
        Employee source = new Employee();
        source.setName("Ana");
        source.setAgeAsString("30");
        source.setGenderCode("F");
        source.setHireDate(LocalDate.of(2020, 1, 15));
        // ... configuração de outros campos

        EmployeeDTO target = new EmployeeDTO();
        target.setIdade(25); // Valor pré-existente

        BeanUtils.copyProperties(source, target);
        log.info("Target after copy: {}", target);
        // Saída esperada: campos 'nome' (String para String), 'competencias' (mesmo nome/tipo)
        // Campos 'idade', 'genero', 'cpf', 'dataAdmissaoFormatada' não são copiados.
        // O campo 'endereco' recebe uma referência (cópia rasa).
    }
}

Limitações: Nenhuma conversão automática de tipos (ex: String para Integer), nomes de campos devem ser idênticos, e não permite lógica customizada durante o mapeamento.

Abordaegm do MapStruct

MapStruct permite mapear explicitamente campos com nomes ou tipos diferentes usando anotações.

@Mapper(componentModel = "spring", nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface EmployeeMapper {

    @Mapping(source = "name", target = "nome")
    @Mapping(source = "ageAsString", target = "idade", qualifiedByName = "stringToInteger")
    @Mapping(source = "genderCode", target = "genero", qualifiedByName = "firstCharExtractor")
    @Mapping(source = "taxId", target = "cpf", qualifiedByName = "stringToLong")
    @Mapping(source = "hireDate", target = "dataAdmissaoFormatada", qualifiedByName = "localDateToString")
    EmployeeDTO toDTO(Employee employee);

    @Named("stringToInteger")
    default Integer convertStringToInteger(String value) {
        return value == null ? null : Integer.valueOf(value);
    }

    @Named("stringToLong")
    default Long convertStringToLong(String value) {
        return value == null ? null : Long.parseLong(value);
    }

    @Named("firstCharExtractor")
    default Character extractFirstChar(String value) {
        return (value != null && !value.isEmpty()) ? value.charAt(0) : null;
    }

    @Named("localDateToString")
    default String formatDate(LocalDate date) {
        return date == null ? null : date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
    }
}

O código de implementação é gerado automaticamente durante a compilação e incluirá todas as conversões e verificações de nulidade definidas.

Implementação Detalhada

Anotação @Mapper

A anotação @Mapper marca uma interface como um mapper. Seu atributo mais importante é componentModel.

  • componentModel = "default" (padrão): Cria uma instância singleton acessível via Mappers.getMapper(SuaClasse.class). É thread-safe.
  • componentModel = "spring": Gera uma classe anotada com @Component, permitindo injeção de dependência via @Autowired.
  • componentModel = "cdi": Para Jakarta EE / CDI.
  • componentModel = "jsr330": Para ambientes com injeção baseada no JSR-330.

Anotações @Mapping e @Mappings

A anotação @Mapping é usada para definir regras de mapeamento para campos específicos. @Mappings agrupa múltiplas anotações @Mapping.

@Mapping(target = "nomeCompleto", expression = "java(employee.getFirstName() + \" \" + employee.getLastName())")
@Mapping(source = "birthDate", target = "idade", qualifiedByName = "calculateAge")
@Mapping(target = "setorDescricao", expression = "java(employee.getDepartment().getDescription())")
@Mapping(source = "email", target = "enderecoEmail", defaultValue = "não informado")
@Mapping(target = "id", ignore = true) // Ignora o campo 'id' no destino
@Mapping(target = "sistemaOrigem", constant = "SISTEMA_RH")

Principais Atributos:

  • source / target: Define a propriedade de origem e destino. Necessário quando os nomes diferem.
  • expression: Permite executar uma expressão Java para calcular o valor do destino.
  • qualifiedByName: Especifica um método customizado (anotado com @Named) para a conversão.
  • dateFormat: Define um padrão para conversão entre Date/LocalDate e String.
  • defaultValue: Valor padrão caso a fonte seja nula.
  • ignore: true para não mapear a propriedade de destino.
  • constant: Atribui um valor constante (literal) à propriedade de destino.

Regra Importante: Os atributos source, expression e constant são mutuamente exclusivos em uma mesma anotação @Mapping.

Tags: MapStruct java Mapeamento de Objetos DTOs Beans

Publicado em 6-13 18:58 por Thomas