Ignorando propriedades não mapeadas com MapStruct

Ignorando propriedades não mapeadas com MapStruct

1. Visão geral

Em aplicativos Java,we may wish to copy values from one type of Java bean to another. Para evitar um código longo e sujeito a erros, podemos usar um mapeador de bean comoMapStruct.

Embora o mapeamento de campos idênticos com nomes de campo idênticos seja muito simples,we often encounter mismatched beans. Neste tutorial, veremos como MapStruct lida com o mapeamento parcial.

2. Mapeamento

MapStruct é um processador de anotação Java. Portanto, tudo o que precisamos fazer é definir a interface do mapeador e declarar métodos de mapeamento. MapStruct will generate an implementation of this interface during compilation.

Para simplificar, vamos começar com duas classes com os mesmos nomes de campo:

public class CarDTO {
    private int id;
    private String name;
}
public class Car {
    private int id;
    private String name;
}

A seguir, vamos criar uma interface de mapeador:

@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    CarDTO carToCarDTO(Car car);
}

Finalmente, vamos testar nosso mapeador:

@Test
public void givenCarEntitytoCar_whenMaps_thenCorrect() {
    Car entity = new Car();
    entity.setId(1);
    entity.setName("Toyota");

    CarDTO carDto = CarMapper.INSTANCE.carToCarDTO(entity);

    assertThat(carDto.getId()).isEqualTo(entity.getId());
    assertThat(carDto.getName()).isEqualTo(entity.getName());
}

3. Propriedades não mapeadas

Como o MapStruct opera em tempo de compilação, pode ser mais rápido que uma estrutura de mapeamento dinâmico. Ele também podegenerate error reports if mappings are incomplete - ou seja, se nem todas as propriedades de destino forem mapeadas:

Warning:(X,X) java: Unmapped target property: "propertyName".

Embora este seja um aviso útil no caso de um acidente, podemos preferir lidar com as coisas de maneira diferente se os campos estiverem faltando de propósito.

Vamos explorar isso com um exemplo de mapeamento de dois objetos simples:

public class DocumentDTO {
    private int id;
    private String title;
    private String text;
    private List comments;
    private String author;
}
public class Document {
    private int id;
    private String title;
    private String text;
    private Date modificationTime;
}

Temos campos exclusivos em ambas as classes que não devem ser preenchidos durante o mapeamento. Eles são:

  • comments emDocumentDTO

  • author emDocumentDTO

  • modificationTime emDocument

Se definirmos uma interface do mapeador, isso resultará em mensagens de aviso durante a construção:

@Mapper
public interface DocumentMapper {
    DocumentMapper INSTANCE = Mappers.getMapper(DocumentMapper.class);

    DocumentDTO documentToDocumentDTO(Document entity);
    Document documentDTOToDocument(DocumentDTO dto);
}

Como não queremos mapear esses campos, podemos excluí-los do mapeamento de algumas maneiras.

4. Ignorando campos específicos

Para pular várias propriedades em um método de mapeamento específico, podemosuse the ignore property in the @Mapping annotation:

@Mapper
public interface DocumentMapperMappingIgnore {

    DocumentMapperMappingIgnore INSTANCE =
      Mappers.getMapper(DocumentMapperMappingIgnore.class);

    @Mapping(target = "comments", ignore = true)
    @Mapping(target = "author", ignore = true)
    DocumentDTO documentToDocumentDTO(Document entity);

    @Mapping(target = "modificationTime", ignore = true)
    Document documentDTOToDocument(DocumentDTO dto);
}

Aqui, fornecemos o nome do campo comotargete definimosignore paratrue para mostrar que não é necessário para o mapeamento.

No entanto, essa técnica não é conveniente para alguns casos. Podemos achar difícil usar, por exemplo, ao usar modelos grandes com um grande número de campos.

5. Política de destino não mapeada

Para tornar as coisas mais claras e o código mais legível, podemosspecify the unmapped target policy.

Para fazer isso, usamos o MapStructunmappedTargetPolicy para fornecer nosso comportamento desejado quando não há campo de origem para o mapeamento:

  • ERROR: qualquer propriedade de destino não mapeada falhará na compilação - isso pode nos ajudar a evitar campos acidentalmente não mapeados

  • WARN: (padrão) mensagens de aviso durante a construção

  • IGNORE: sem saída ou erros

In order to ignore unmapped properties and get no output warnings, devemosassign the IGNORE value to the unmappedTargetPolicy. Existem várias maneiras de fazer isso, dependendo do propósito.

5.1. Defina uma política em cadaMapper

Podemos definir a anotaçãothe unmappedTargetPolicy to the@Mapper. Como resultado, todos os seus métodos ignoram propriedades não mapeadas:

@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DocumentMapperUnmappedPolicy {
    // mapper methods
}

5.2. Use umMapperConfig compartilhado

Podemos ignorar propriedades não mapeadas em vários mapeadores configurandothe unmappedTargetPolicy via@MapperConfig para compartilhar uma configuração entre vários mapeadores.

Primeiro, criamos uma interface anotada:

@MapperConfig(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface IgnoreUnmappedMapperConfig {
}

Em seguida, aplicamos essa configuração compartilhada a um mapeador:

@Mapper(config = IgnoreUnmappedMapperConfig.class)
public interface DocumentMapperWithConfig {
    // mapper methods
}

Devemos observar que este é um exemplo simples que mostra o uso mínimo de@MapperConfig, que pode não parecer muito melhor do que definir a política em cada mapeador. A configuração compartilhada se torna muito útil quando há várias configurações para padronizar em vários mapeadores.

5.3. Opções de configuração

Finalmente, podemos configurar as opções do processador de anotação do gerador de código MapStruct. Ao usarMaven, podemos passar as opções do processador usando o parâmetrocompilerArgs do plug-in do processador:


    
        
            org.apache.maven.plugins
            maven-compiler-plugin
            ${maven-compiler-plugin.version}
            
                ${maven.compiler.source}
                ${maven.compiler.target}
                
                    
                        org.mapstruct
                        mapstruct-processor
                        ${org.mapstruct.version}
                    
                
                
                    
                        -Amapstruct.unmappedTargetPolicy=IGNORE
                    
                
            
        
    

Neste exemplo, estamos ignorando as propriedades não mapeadas em todo o projeto.

6. A Ordem de Precedência

Vimos várias maneiras que podem nos ajudar a lidar com mapeamentos parciais e ignorar completamente as propriedades não mapeadas. Também vimos como aplicá-los independentemente em um mapeador, maswe can also combine them.

Vamos supor que temos uma grande base de código de beans e mapeadores com a configuração MapStruct padrão. Não queremos permitir mapeamentos parciais, exceto em alguns casos. Podemos facilmente adicionar mais campos a um bean ou a sua contrapartida mapeada e obter um mapeamento parcial sem notá-lo.

Portanto, é provavelmente uma boa ideia adicionar uma configuração global por meio da configuração do Maven para fazer a compilação falhar no caso de mapeamentos parciais.

Agora, para permitir propriedades não mapeadas em alguns de nossos mapeadores eoverride the global behavior, podemos combinar as técnicas, tendo em mente a ordem de precedência (do maior para o menor):

  • Ignorando Campos Específicos no Nível do Método do Mapeador

  • A política no mapeador

  • O MapperConfig compartilhado

  • A configuração global

7. Conclusão

Neste tutorial,we looked at how to configure MapStruct to ignore unmapped properties.

Primeiro, analisamos o que as propriedades não mapeadas significam para o mapeamento. Então vimos como mapeamentos parciais poderiam ser permitidos sem erros, de algumas maneiras diferentes.

Por fim, aprendemos a combinar essas técnicas, tendo em mente a ordem de precedência.

Como sempre, o código deste tutorial está disponívelover on GitHub.