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
- pom.xml
- Premisas
- Instanciando um mapeador
- Injetando um mapeador via Spring (Boot)
- Mapeando variáveis com o mesmo nome
- Verificando a implementação gerada pelo MapStruct
- Mapeando variáveis com nomes divergentes
- Mapeando variáveis de tipos numéricos e String
- Mapeando coleções
- Mapeando variável com valor devolvido por método
- Mapeando variável com valor constante
- Mapeando variável nula com valor padrão
- Mapeando múltiplos níveis de variáveis
- Mapeando variáveis aninhadas
- Invertendo mapeamentos
- Importando mapeamentos
- Implementando mapeamentos
- Mapeando múltiplos objetos em um
- Atualizando objetos
- Garantindo que todas as variáveis estejam mapeadas
- Referências
👉 Algum conceito faltando?
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
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.
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
- Anote uma interface ou classe abstrata com
@Mapper
. - 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)
Anote uma interface ou classe abstrata com
@Mapper
.Em seu atributo
componentModel
informespring
.import org.mapstruct.Mapper; @Mapper(componentModel = "spring") public interface MapeadorInjetavel {}
Injete o mapeador como qualquer outro componente do Spring Boot.
@Autowired MapeadorInjetavel mapeador;
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
- Implemente uma assinatura de método (como na linha 12) no mapeador com:
- O tipo do objeto a ser lido como argumento (
Entidade
); - e o tipo do objeto a ser instanciado como tipo de retorno (
Resposta
).
- O tipo do objeto a ser lido como argumento (
- O nome do método e do argumento são arbitrários.
- 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
- Após a compilação (p. ex.
mvn clean compile
) as classes estarão disponíveis emtarget/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
- Anote a assinatura de método com
@Mapping
. - No atributo
source
informe a propriedade a ser copiada. - No atributo
target
a propriedade que será escrita. - 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
- 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
- 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
- Informe o atributo
imports
na anotação@Mapper
caso a expressão utilize algum tipo que precise ser importado (linha 15). - Anote a assinuatura de método com
@Mapping
. - No atributo
target
a propriedade que será escrita. - No atributo
expression
, dentro dejava()
, 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
- Anote o método com
@Mapping
. - No atributo
constant
o valor constante a ser escrito. - 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
- Anote o método com
@Mapping
. - Inclua o atributo
defaultValue
para valor constante oudefaultExpression
para valor resultante de uma expressão. - No atributo
target
informe a propriedade que receberá: o valor desource
ou o valor padrão caso aquele seja nulo. - 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
- Anote a assinatura de método com
@Mapping
apenas para cada par de propriedades cujos nomes diferem. - No atributo
source
informe a propriedade utilizando a notação de ponto.- MapStruct infere o mapeamento entre cada uma das referências denotadas na notação de ponto. Note que não é preciso mapear espeficiamente
departamento
edpto
.
- MapStruct infere o mapeamento entre cada uma das referências denotadas na notação de ponto. Note que não é preciso mapear espeficiamente
- 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
- Anote o método mapeador com
@Mapping
. - No atributo
source
informe a propriedade que contenha propriedades aninhadas. - Em
target
atribua.
.- Com isso todos os atributos aninhados em
source
serão mapeados para atributos do tipo alvo.
- Com isso todos os atributos aninhados em
- 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
- Crie um mapeamento invertendo o tipo do retorno e tipo do parâmetro de um mapeamento existente.
- 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
- 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
- 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
- Defina a assinatura de método com os múltiplos tipos fonte na lista de parâmetros.
- Inclua
@Mapping
caso necessário.- Comece o atributo
source
especificando o nome do parâmetro e então a variável a ser mapeada.
- Comece o atributo
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
- Crie o método mapeador sem tipo de retorno.
- Inclua os tipos fonte e alvo na lista de parâmetros.
- Anote o tipo alvo com
@MappingTarget
. @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
- Na anotação
@Mapper
defina o atributounmappedTargetPolicy
com a enumReportingPolicy.ERROR
. - 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".