Livro de receitas MapStruct

utilizando Java 11 e Lombok

Cada receita exemplifica um tópico do MapStruct

MapStruct é um processador de anotações que gera implementações de mapeamentos. Mapeamentos definem como transferir dados de um objeto para outro.

Essas implementações são derivadas de assinaturas de método em interfaces ou classe abstratas e de convenções na leitura e escrita em variáveis. Os mapeamentos que divergem das convenções podem ser configurados por meio de anotações.

Conteúdo

  1. pom.xml
  2. Premisas
  3. Instanciando um mapeador
  4. Injetando um mapeador via Spring (Boot)
  5. Mapeando variáveis com o mesmo nome
  6. Verificando a implementação gerada pelo MapStruct
  7. Mapeando variáveis com nomes divergentes
  8. Mapeando variáveis de tipos numéricos e String
  9. Mapeando coleções
  10. Mapeando variável com valor devolvido por método
  11. Mapeando variável com valor constante
  12. Mapeando variável nula com valor padrão
  13. Mapeando múltiplos níveis de variáveis
  14. Mapeando variáveis aninhadas
  15. Invertendo mapeamentos
  16. Importando mapeamentos
  17. Implementando mapeamentos
  18. Mapeando múltiplos objetos em um
  19. Atualizando objetos
  20. Garantindo que todas as variáveis estejam mapeadas
  21. Referências

👉 Algum conceito faltando?

Deixe um comentário no fim do post e escreverei a receita assim que possível.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>dev.caiosantesso</groupId>
    <artifactId>mapstruct</artifactId>
    <version>2021.5.0</version>

    <properties>
        <project.build.sourceEncoding>UTF-8
        </project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
        <org.projectlombok.version>1.18.16</org.projectlombok.version>
        <lombok-mapstruct-binding.version>0.2.0
        </lombok-mapstruct-binding.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${org.projectlombok.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor
                            </artifactId>
                            <version>${org.mapstruct.version}
                            </version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${org.projectlombok.version}
                            </version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok-mapstruct-binding
                            </artifactId>
                            <version>
                                ${lombok-mapstruct-binding.version}
                            </version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Premisas

  1. As propriedades a serem mapeadas de ambos os objetos devem ser públicas, sendo:

    • A propriedade fonte uma variável ou um getter;
    • e a propriedade alvo uma variável, um argumento de construtor ou um setter.
  2. O mapeamento entre propriedades com nomes iguais é implícita: nenhuma anotação é necessária, bastando apenas uma assinatura de método com o tipo a ser lido como argumento e o tipo a ser instanciado e escrito como tipo de retorno.

Instanciando um mapeador

  1. Anote uma interface ou classe abstrata com @Mapper.
  2. Atribua o retorno de Mappers.getMapper(Classe.class) a uma constante já que cada vez que esse método é chamado uma nova instância é criada.
    • Opcionalmente declare um método estático para devolver a constante.
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper
interface MapeadorViaInterface {
  MapeadorViaInterface INSTANCIA =
      Mappers.getMapper(MapeadorViaInterface.class);

  static MapeadorViaInterface meuMapeador() { return INSTANCIA; }
}
@Mapper
abstract class MapeadorViaClasseAbstrata {
  static MapeadorViaClasseAbstrata INSTANCIA =
      Mappers.getMapper(MapeadorViaClasseAbstrata.class);

  public static MapeadorViaClasseAbstrata meuMapeador() {
    return INSTANCIA;
  }
}

Injetando um mapeador via Spring (Boot)

  1. Anote uma interface ou classe abstrata com @Mapper.

  2. Em seu atributo componentModel informe spring.

    import org.mapstruct.Mapper;
    
    @Mapper(componentModel = "spring")
    public interface MapeadorInjetavel {}
  3. Injete o mapeador como qualquer outro componente do Spring Boot.

    @Autowired
    MapeadorInjetavel mapeador;
    
  4. Quando o projeto for executado, o mapeador será criado e anotado com @Component.

    package dev.caiosantesso.mapstruct;
    
    import javax.annotation.processing.Generated;
    import org.springframework.stereotype.Component;
    
    @Generated(
        value = "org.mapstruct.ap.MappingProcessor",
        date = "2021-05-20T22:06:20-0300",
        comments = "version: 1.4.2.Final, compiler: javac, ..."
    )
    @Component
    public class MapeadorInjetavelImpl implements MapeadorInjetavel {
    }
    

Mapeando variáveis com o mesmo nome

  1. Implemente uma assinatura de método (como na linha 12) no mapeador com:
    1. O tipo do objeto a ser lido como argumento (Entidade);
    2. e o tipo do objeto a ser instanciado como tipo de retorno (Resposta).
  2. O nome do método e do argumento são arbitrários.
  3. Todas as propriedades comuns aos dois objetos serão copiadas.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package dev.caiosantesso.mapstruct;

import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper
interface Mapeador {
    Mapeador INSTANCIA = Mappers.getMapper(Mapeador.class);

    static Mapeador obtenhaInstancia() {
        return INSTANCIA;
    }

    Resposta entidadeParaResposta(Entidade entidade);
}

class Entidade {
    String nome = "Joshua Bloch";

    public String getNome() {
        return nome;
    }
}

class Resposta {
    String nome;

    public void setNome(String nome) {
        this.nome = nome;
    }
}

public class MapeamentoSimples {
    public static void main(String[] args) {
        Mapeador mapeador = Mapeador.obtenhaInstancia();
        Entidade ent = new Entidade();
        Resposta resposta = mapeador.entidadeParaResposta(ent);
        System.out.println(resposta.nome); //Exibe Joshua Bloch.
    }
}

Verificando a implementação gerada pelo MapStruct

  1. Após a compilação (p. ex. mvn clean compile) as classes estarão disponíveis em target/generated-sources/annotations/<Pacote>/<NomeDaInterface>Impl.java.
package dev.caiosantesso.mapstruct;

import javax.annotation.processing.Generated;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2021-05-19T21:58:52-0300",
    comments = "version: 1.4.2.Final, compiler: javac, ..."
)
class MapeadorImpl implements Mapeador {

    @Override
    public Resposta entidadeParaResposta(Entidade entidade) {
        if ( entidade == null ) {
            return null;
        }

        Resposta resposta = new Resposta();

        resposta.setNome( entidade.getNome() );

        return resposta;
    }
}

Mapeando variáveis com nomes divergentes

  1. Anote a assinatura de método com @Mapping.
  2. No atributo source informe a propriedade a ser copiada.
  3. No atributo target a propriedade que será escrita.
  4. Repita para cada propriedade divergente.
package dev.caiosantesso.mapstruct.variavel.diferente;

import lombok.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import static dev.caiosantesso.mapstruct.variavel.diferente.PessoaMapeador.mapeador;

@Mapper
interface PessoaMapeador {
  PessoaMapeador INSTANCIA = Mappers.getMapper(PessoaMapeador.class);

  static PessoaMapeador mapeador() { return INSTANCIA; }

  @Mapping(source = "apelido", target = "alcunha")
  PessoaResposta entidadeParaResposta(PessoaEntidade entidade);
}

@AllArgsConstructor
@Getter
class PessoaEntidade {
  String apelido;
  String nome;
  public int getIdade(){ return 59;}
}

@Setter
@ToString
class PessoaResposta {
  String alcunha;
  String nome;
  int idade;
}

public class MapeamentoComVariaveisDiferentes {
  public static void main(String[] args) {
    PessoaEntidade ent = new PessoaEntidade("Josh", "Joshua Bloch");
    PessoaResposta resposta = mapeador().entidadeParaResposta(ent);
    System.out.println(resposta);
    // Exibe PessoaResposta(alcunha=Josh, nome=Joshua Bloch,
    // idade=59)
  }
}

Mapeando variáveis de tipos numéricos e String

  1. Nenhuma anotação é necessária.

Tipos primitivos numéricos, seus wrappers, BigDecimal, BigInteger e String são convertidos implicitamente entre si.

⚠️ A programadora deve garantir que o valor cabe na variável alvo, como no caso de um long ser convertido para um byte.

package dev.caiosantesso.mapstruct.variavel.tipos;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

import java.math.BigDecimal;

import static dev.caiosantesso.mapstruct.variavel.tipos.Mapeador.mapeador;

@Mapper
interface Mapeador {
  Mapeador INSTANCIA = Mappers.getMapper(Mapeador.class);

  static Mapeador mapeador() { return INSTANCIA; }

  Resposta entidadeParaResposta(Entidade entidade);
}

@RequiredArgsConstructor
@Getter
class Entidade {
  final double preco;
  final String quantidade;
  final short status;
  final long codigo;
}

@Setter
@ToString
class Resposta {
  BigDecimal preco;
  int quantidade;
  Long status;
  String codigo;
}

public class MapeamentoEntreStringsETiposNumericos {
  public static void main(String[] args) {
    Entidade e = new Entidade(22.15, "4", (short) 200, 64L);
    System.out.println(mapeador().entidadeParaResposta(e));
    // Resposta(preco=22.15, quantidade=4, status=200, codigo=64)
  }
}

Mapeando coleções

  1. Declare um mapeamento para os tipos que contêm as coleções.

MapStruct converte os elementos de uma coleção aplicando as mesmas regras da conversão entre tipos de variáveis.

package dev.caiosantesso.mapstruct.variavel.colecoes;

import lombok.*;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

import static dev.caiosantesso.mapstruct.variavel.colecoes.Mapeador.mapeador;


@Mapper
interface Mapeador {
  Mapeador INSTANCIA = Mappers.getMapper(Mapeador.class);

  static Mapeador mapeador() { return INSTANCIA; }

  Resposta entidadeParaResposta(Entidade entidade);
}

@RequiredArgsConstructor
@Getter
class CPU {
  final String modelo;
}

@Setter
@ToString
class Processador {
  String modelo;
}

@RequiredArgsConstructor
@Getter
class Entidade {
  final List<CPU> processadores;
  final Map<String, Double> precos;
}

@Setter
@ToString
class Resposta {
  List<Processador> processadores;
  Map<Byte, BigDecimal> precos;
}

public class MapeamentoDeColecoes {
  public static void main(String[] args) {
    List<CPU> cpus = List.of(new CPU("i5"), new CPU("Ryzen 9"));
    Map<String, Double> precos = Map.of("1", 1150.90, "2", 3379.90);
    Entidade e = new Entidade(cpus, precos);
    System.out.println(mapeador().entidadeParaResposta(e));
    // Resposta(processadores=[Processador(modelo=i5), Processador
    // (modelo=Ryzen 9)], precos={1=1150.9, 2=3379.9})
  }
}

Mapeando variável com valor devolvido por método

  1. Informe o atributo imports na anotação @Mapper caso a expressão utilize algum tipo que precise ser importado (linha 15).
  2. Anote a assinuatura de método com @Mapping.
  3. No atributo target a propriedade que será escrita.
  4. No atributo expression, dentro de java(), informe a expressão a ser executada.
    • para chamar um método do objeto fonte utilize a variável declarada na lista de parâmetros (como na linha 26).
    • alternativamente utilize o tipo qualificado (linha 24) ao invés de imports.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package dev.caiosantesso.mapstruct.variavel.expressao;

import lombok.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import java.time.LocalDateTime;
import java.util.UUID;

import static dev.caiosantesso.mapstruct.variavel.expressao.Mapeador.mapeador;

@Mapper(imports = UUID.class)
interface Mapeador {
  Mapeador INSTANCIA = Mappers.getMapper(Mapeador.class);

  static Mapeador mapeador() { return INSTANCIA; }

  @Mapping(target = "token",
      expression = "java(UUID.randomUUID().toString())")
  @Mapping(target = "expiracao", expression =
      "java(java.time.LocalDateTime.now().plusHours(1))")
  @Mapping(target = "status", expression =
      "java(entidade.getStatus().intValue() + 200)")
  Resposta entidadeParaResposta(Entidade entidade);
}

@Getter
class Entidade {
  Long status = 1L;
}

@Setter
@ToString
class Resposta {
  String token;
  LocalDateTime expiracao;
  int status;
}

public class MapeamentoDeExpressaoParaVariavel {
  public static void main(String[] args) {
    Entidade ent = new Entidade();
    System.out.println(mapeador().entidadeParaResposta(ent));
    //Exibe Resposta(token=7b199e65-16a7-426f-9876-1db97b96cc55,
    // expiracao=2021-05-23T22:45:04.476804, status=201)
  }
}

Mapeando variável com valor constante

  1. Anote o método com @Mapping.
  2. No atributo constant o valor constante a ser escrito.
  3. No atributo target a propriedade que será escrita.
package dev.caiosantesso.mapstruct.variavel.constante;

import lombok.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import java.time.LocalDate;

import static dev.caiosantesso.mapstruct.variavel.constante.Mapeador.mapeador;

@Mapper
interface Mapeador {
  Mapeador INSTANCIA = Mappers.getMapper(Mapeador.class);

  static Mapeador mapeador() { return INSTANCIA; }

  @Mapping(constant = "true", target = "ativo")
  @Mapping(constant = "01.09.18", dateFormat = "dd.MM.yy",
      target = "inicio")
  @Mapping(constant = "5L", target = "quantidade")
  @Mapping(constant = "0xA1", target = "endereco")
  Resposta entidadeParaResposta(Entidade entidade);
}

@RequiredArgsConstructor
@Getter
class Entidade {
  final String local;
}

@Setter
@ToString
class Resposta {
  boolean ativo;
  LocalDate inicio;
  long quantidade;
  int endereco;
  String local;
}

public class MapeamentoDeVariavelParaConstante {
  public static void main(String[] args) {
    Entidade ent = new Entidade("XPTO");
    Resposta resposta = mapeador().entidadeParaResposta(ent);
    System.out.println(resposta);
    //Exibe Resposta(ativo=true, inicio=2018-09-01, quantidade=5,
    // endereco=161, local=XPTO)
  }
}

Mapeando variável nula com valor padrão

  1. Anote o método com @Mapping.
  2. Inclua o atributo defaultValue para valor constante ou defaultExpression para valor resultante de uma expressão.
  3. No atributo target informe a propriedade que receberá: o valor de source ou o valor padrão caso aquele seja nulo.
  4. Opcionalmente inclua o atributo source caso seja o mapeamento de variáveis cujos nomes diferem.
package dev.caiosantesso.mapstruct.variavel.nulo;

import lombok.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import java.time.LocalDate;

import static dev.caiosantesso.mapstruct.variavel.nulo.Mapeador.mapeador;

@Mapper(imports = LocalDate.class)
interface Mapeador {
  Mapeador INSTANCIA = Mappers.getMapper(Mapeador.class);

  static Mapeador mapeador() { return INSTANCIA; }

  @Mapping(source = "codigo", defaultValue = "42", target = "id")
  @Mapping(defaultValue = "Indefinido", target = "status")
  @Mapping(defaultExpression = "java(LocalDate.now())",
      target = "dia")
  Resposta entidadeParaResposta(Entidade entidade);
}

@RequiredArgsConstructor
@Getter
class Entidade {
  final int codigo;
  final String status;
  final LocalDate dia;
}

@Setter
@ToString
class Resposta {
  int id;
  String status;
  LocalDate dia;
}

public class MapeamentoDeNuloParaVariavel {
  public static void main(String[] args) {
    Entidade ent = new Entidade(5, null, null);
    Resposta resposta = mapeador().entidadeParaResposta(ent);
    System.out.println(resposta);
    //Exibe Resposta(id=5, status=Indefinido, dia=2021-05-23)
  }
}

Mapeando múltiplos níveis de variáveis

  1. Anote a assinatura de método com @Mapping apenas para cada par de propriedades cujos nomes diferem.
  2. No atributo source informe a propriedade utilizando a notação de ponto.
    1. MapStruct infere o mapeamento entre cada uma das referências denotadas na notação de ponto. Note que não é preciso mapear espeficiamente departamento e dpto.
  3. Idem para o atributo target.
package dev.caiosantesso.mapstruct.variavel.referencia;

import lombok.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import static dev.caiosantesso.mapstruct.variavel.referencia.Mapeador.mapeador;

@Mapper
interface Mapeador {
  Mapeador INSTANCIA = Mappers.getMapper(Mapeador.class);

  static Mapeador mapeador() { return INSTANCIA; }

  //Redundante: @Mapping(source = "departamento", target = "dpto")
  @Mapping(source = "departamento.funcionarios",
      target = "dpto.pessoal")
  Resposta entidadeParaResposta(Entidade entidade);
}

@Getter
class DepartamentoEntidade {
  String nome = "TI";
  int funcionarios = 30;
}

@Setter
@ToString
class DepartamentoResposta {
  String nome;
  int pessoal;
}

@RequiredArgsConstructor
@Getter
class Entidade {
  final DepartamentoEntidade departamento;
}

@Setter
@ToString
class Resposta {
  DepartamentoResposta dpto;
}

public class MapeamentoEntreReferencias {
  public static void main(String[] args) {
    Entidade ent = new Entidade(new DepartamentoEntidade());
    System.out.println(mapeador().entidadeParaResposta(ent));
    //Exibe Resposta(departamento=DepartamentoResposta
    // (nome=TI, funcionarios=30))
  }
}

Mapeando variáveis aninhadas

  1. Anote o método mapeador com @Mapping.
  2. No atributo source informe a propriedade que contenha propriedades aninhadas.
  3. Em target atribua ..
    1. Com isso todos os atributos aninhados em source serão mapeados para atributos do tipo alvo.
  4. Em caso de ambiguidade entre as propriedades, crie o mapeamento entre elas explicitamente como na linha 18.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package dev.caiosantesso.mapstruct.variavel.aninhada;

import lombok.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import static dev.caiosantesso.mapstruct.variavel.aninhada.Mapeador.mapeador;

@Mapper
interface Mapeador {
  Mapeador INSTANCIA = Mappers.getMapper(Mapeador.class);

  static Mapeador mapeador() { return INSTANCIA; }

  @Mapping(source = "endereco", target = ".")
  @Mapping(source = "pessoa", target = ".")
  @Mapping(source = "pessoa.id", target = "id")
  Resposta entidadeParaResposta(Entidade entitidade);
}

@AllArgsConstructor
@Getter
class Endereco {
  long id;
  String logradouro;
  int numero;
}

@AllArgsConstructor
@Getter
class Pessoa {
  long id;
  String nome;
}

@RequiredArgsConstructor
@Getter
class Entidade {
  final Endereco endereco;
  final Pessoa pessoa;
}

@Setter
@ToString
class Resposta {
  long id;
  String logradouro;
  int numero;
  String nome;
}

public class MapeandoVariaveisAninhadas {
  public static void main(String[] args) {
    Entidade ent = new Entidade(new Endereco(11, "R Java", 25),
        new Pessoa(16,"James Gosling"));
    System.out.println(mapeador().entidadeParaResposta(ent));
    //Exibe Resposta(id=16, logradouro=R Java, numero=25,
    // nome=James Gosling)
  }
}

Invertendo mapeamentos

  1. Crie um mapeamento invertendo o tipo do retorno e tipo do parâmetro de um mapeamento existente.
  2. Anote-o com @InheritInverseConfiguration.

@InheritInverseConfiguration não funciona devidamente quando há ambiguidades nos mapeamentos, como no caso de propriedaes aninhadas que contenham o mesmo nome.

package dev.caiosantesso.mapstruct.mapeamento.inversao;

import lombok.Data;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import static dev.caiosantesso.mapstruct.mapeamento.inversao.Mapeador.mapeador;

@Mapper
interface Mapeador {
  Mapeador INSTANCIA = Mappers.getMapper(Mapeador.class);

  static Mapeador mapeador() { return INSTANCIA; }

  @Mapping(source = "endereco", target = ".")
  @Mapping(source = "pessoa", target = ".")
  Resposta entidadeParaResposta(Entidade entitidade);

  @InheritInverseConfiguration
  Entidade respostaParaEntidade(Resposta resposta);
}

@Data
class Endereco {
  String logradouro;
  int numero;
}

@Data
class Pessoa {
  String nome;
}

@Data
class Entidade {
  final Endereco endereco;
  final Pessoa pessoa;
}

@Data
class Resposta {
  final String logradouro;
  final int numero;
  final String nome;
}

public class InvertendoMapeamentos {
  public static void main(String[] args) {
    Resposta re = new Resposta("R Java", 25, "Gosling, James");
    System.out.println(mapeador().respostaParaEntidade(re));
    //Exibe Entidade(endereco=Endereco(logradouro=R Java,
    // numero=25), pessoa=Pessoa(nome=Gosling, James))
  }
}

Importando mapeamentos

  1. Informe a classe mapeadora a ser importada no atributo uses da anotação @Mapper.
package dev.caiosantesso.mapstruct.mapeador.reutilizacao;

import lombok.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import static dev.caiosantesso.mapstruct.mapeador.reutilizacao.Mapeador.mapeador;

@Mapper
interface EnderecoMapeador {
  @Mapping(source = "numero", target = "num")
  @Mapping(source = "logradouro", target = "rua")
  EnderecoResposta entParaResposta(EnderecoEntidade entidade);
}

@Mapper(uses = EnderecoMapeador.class)
interface Mapeador {
  Mapeador INSTANCIA = Mappers.getMapper(Mapeador.class);

  static Mapeador mapeador() { return INSTANCIA; }

  @Mapping(source = "endereco", target = "local")
  Resposta entidadeParaResposta(Entidade entidade);
}

@Getter
class EnderecoEntidade {
  String logradouro = "R Java";
  int numero = 25;
}

@Setter
@ToString
class EnderecoResposta {
  String rua;
  int num;
}

@RequiredArgsConstructor
@Getter
class Entidade {
  final EnderecoEntidade endereco;
}

@Setter
@ToString
class Resposta {
  EnderecoResposta local;
}

public class ImportandoMapeadores {
  public static void main(String[] args) {
    Entidade ent = new Entidade(new EnderecoEntidade());
    System.out.println(mapeador().entidadeParaResposta(ent));
    //Exibe Resposta(local=EnderecoResposta(rua=R Java, num=25))
  }
}

Implementando mapeamentos

  1. Implemente um método default com o tipo a ser copiado como argumento e o objeto a ser criado como retorno.
    • MapStruct chamará o método da mesma forma que os métodos gerados automaticamente.
package dev.caiosantesso.mapstruct.mapeamento.implementacao;

import lombok.*;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

import java.util.Arrays;

import static dev.caiosantesso.mapstruct.mapeamento.implementacao.Mapeador.mapeador;

@Mapper
interface Mapeador {
  Mapeador INSTANCIA = Mappers.getMapper(Mapeador.class);

  static Mapeador mapeador() { return INSTANCIA; }

  default Resposta entidadeParaResposta(Entidade entidade) {
    double media = Arrays.stream(entidade.getNotas()).average()
        .orElseThrow();
    return Resposta.builder().media(media).build();
  }
}

@RequiredArgsConstructor
@Getter
class Entidade {
  final int[] notas;
}

@Builder
@Setter
@ToString
class Resposta {
  final double media;
}

public class ImplementandoMapeamento {
  public static void main(String[] args) {
    Entidade ent = new Entidade(new int[]{4, 2, 0});
    System.out.println(mapeador().entidadeParaResposta(ent));
    //Exibe Resposta(media=2.0)
  }
}

Mapeando múltiplos objetos em um

  1. Defina a assinatura de método com os múltiplos tipos fonte na lista de parâmetros.
  2. Inclua @Mapping caso necessário.
    1. Comece o atributo source especificando o nome do parâmetro e então a variável a ser mapeada.
package dev.caiosantesso.mapstruct.mapeamento.composicao;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import static dev.caiosantesso.mapstruct.mapeamento.composicao.Mapeador.mapeador;

@Mapper
interface Mapeador {
  Mapeador INSTANCIA = Mappers.getMapper(Mapeador.class);

  static Mapeador mapeador() { return INSTANCIA; }

  @Mapping(source = "p.nome", target = "paciente")
  @Mapping(source = "m.nome", target = "medico")
  Consulta paraConsulta(Paciente p, Medico m);
}

@AllArgsConstructor
@Getter
class Paciente {
  String nome;
}

@RequiredArgsConstructor
@Getter
class Medico {
  final String nome;
  final int crm;
}

@AllArgsConstructor
@ToString
class Consulta {
  String medico;
  int crm;
  String paciente;
}

public class MapeamentoMultiplasFontes {
  public static void main(String[] args) {
    Medico m = new Medico("Brian Goetz", 751);
    Paciente p = new Paciente("Torvalds");
    System.out.println(mapeador().paraConsulta(p, m));
    //Consulta(medico=Brian Goetz, crm=751, paciente=Torvalds)
  }
}

Atualizando objetos

  1. Crie o método mapeador sem tipo de retorno.
  2. Inclua os tipos fonte e alvo na lista de parâmetros.
  3. Anote o tipo alvo com @MappingTarget.
  4. @Mapping pode ser utilizado como nos mapeamentos de cópia.
package dev.caiosantesso.mapstruct.mapeamento.atualizacao;

import lombok.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;

import static dev.caiosantesso.mapstruct.mapeamento.atualizacao.Mapeador.MAPEADOR;

@Mapper
interface Mapeador {
  Mapeador MAPEADOR = Mappers.getMapper(Mapeador.class);

  @Mapping(constant = "1.4.2", target = "versao")
  void atualize(Entidade ent, @MappingTarget Resposta resposta);
}

@AllArgsConstructor
@Getter
@ToString
class Entidade {
  boolean ativo;
  String descricao;
}

@Setter
@ToString
class Resposta {
  boolean ativo = false;
  String descricao = "Nenhuma";
  String versao;
}

public class MapeamentoParaAtualizacao {
  public static void main(String[] args) {
    Resposta re = new Resposta();
    System.out.println(re);
    // Resposta(ativo=false, descricao=Nenhuma, versao=null)
    MAPEADOR.atualize(new Entidade(true, "MapStruct"), re);
    System.out.println(re);
    // Resposta(ativo=true, descricao=MapStruct, versao=1.4.2)
  }
}

Garantindo que todas as variáveis estejam mapeadas

  1. Na anotação @Mapper defina o atributo unmappedTargetPolicy com a enum ReportingPolicy.ERROR.
  2. Um erro de compilação ocorrerá caso o tipo alvo tenha uma propriedade não mapeada.
package dev.caiosantesso.mapstruct.mapeamento.requerido;

import lombok.*;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;

@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
interface Mapeador {
  Resposta entidadeParaResposta(Entidade entidade);
}

@AllArgsConstructor
@Getter
class Entidade {
  double altura;
}

@Setter
class Resposta {
  String nome;
  double altura;
}

public class MapeandoTodasAsVariaveis {
  public static void main(String[] args) {
  }
}

Trecho de mvn compile abaixo.

[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ mapstruct ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 18 source files to /home/caio/workspaces/blog/mapstruct/target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
[ERROR] /home/caio/workspaces/blog/mapstruct/src/main/java/dev/caiosantesso/mapstruct/mapeamento/requerido/MapeandoTodasAsVariaveis.java:[9,12] Unmapped target property: "nome".

Referências

Escrito por Caio Santesso.

Comentários

  • Conteúdo dos posts, exceto onde indicado contrário, licenciado sob a licença CC BY-SA 4.0 .