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 viaMappers.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 entreDate/LocalDateeString.defaultValue: Valor padrão caso a fonte seja nula.ignore:truepara 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.