Back-endDesenvolvimentoFerramentas

MapStruct: A biblioteca que falta no seu projeto Java

Você já se viu perdido em meio a uma selva de código, tentando converter objetos Java de um tipo para outro? Se a resposta é sim, então você precisa continuar lendo, pois queremos acabar de vez com essa dor de cabeça. Neste artigo, vamos falar sobre o MapStruct, uma ferramenta brilhante que faz da conversão de objetos Java uma tarefa tão fácil quanto tomar uma xícara de café!

O que é MapStruct e o que ele faz?

MapStruct é uma biblioteca Java criada com o propósito de evitar o boilerplate code, que muitas vezes polui nosso código e torna a manutenção complicada. Ela simplifica muito o processo de mapeamento de objetos, pois apenas com interfaces Java e anotações, podemos definir as regras de mapeamento entre as classes e gerar automaticamente o código quando a aplicação é compilada. Assim, você não precisa criar várias classes builders manualmente para construir instâncias dos seus objetos. Isso é muito bom quando você possui muitas entidades na sua aplicação e precisa fazer muitas transformações para DTO’s e vice-versa.

Como utilizar o MapStruct?

Para exemplificar, vamos utilizar um exemplo bem simples e direto ao ponto. Não criaremos um projeto e nem discutiremos sobre frameworks e gerenciadores de dependências, então vamos apenas assumir que temos um projeto fictício com Spring Boot e Maven. Caso não esteja utilizando o Maven em seu projeto, você pode procurar como configurar o MapStruct de acordo com o seu gerenciador de dependências.

No nosso caso, com o Maven, precisamos apenas adicionar a dependência do MapStruct e configurar o maven-compiler-plugin com o processador de anotações do MapStruct no arquivo pom.xml.

<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.5.3.Final</version> <!-- Utilizar a versão mais recente -->
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>17</source> <!-- Troque pela sua versão do Java -->
                <target>17</target> <!-- Troque pela sua versão do Java -->
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>1.5.3.Final</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

Agora vamos ao código Java!

Imagine que você tem duas classes, Carro e CarroDTO, e quer converter uma instância de Carro em uma instância de CarroDTO. Em um mundo sem MapStruct, você provavelmente criaria manualmente uma instância de CarroDTO setando seus atributos com os valores dos atributos correspondentes no objeto Carro. Mas veja agora como você pode fazer com o MapStruct.

Criamos nossas duas classes que usaremos como exemplo:

public class Carro {
    private String modelo;
    private Integer ano;
    // outros atributos, construtores, getters e setters
}

public class CarroDTO {
    private String modelo;
    private Integer ano;
    // outros atributos, construtores, getters e setters
}

Agora, criamos uma interface com anotações MapStruct para realizar o mapeamento:

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

    CarroDTO toCarroDTO(Carro carro);
}

Por fim, já podemos usar a instância de CarroMapper para fazer o mapeamento, e para isso, vamos apenas criar nosso objeto de origem e chamar nosso mapper.

Carro carro = new Carro();
carro.setModelo("Ferrari 296 GTB");
carro.setAno(2024);

CarroDTO carroDTO = CarroMapper.INSTANCE.toCarroDTO(carro);

Perceba o quanto foi simples converter uma instância de Carro em uma instância de CarroDTO sem precisar setar cada atributo manualmente no objeto de destino.

E quando há muitos atributos e objetos aninhados?

Onde o MapStruct realmente brilha é em situações do mundo real, onde temos objetos complexos com muitos atributos e hierarquias. Vamos imaginar uma aplicação de comércio eletrônico onde precisamos converter entidades de pedidos para DTOs de pedidos.

public class Produto {
    private Long id;
    private String nome;
}

public class Cliente {
    private Long id;
    private String nome;
    private String CPF;
}

public class Pedido {
    private Long id;
    private List<ItemPedido> itens;
    private Cliente cliente;
}

public class ItemPedido {
    private Long id;
    private Produto produto;
    private Integer quantidade;
}

public class PedidoDTO {
    private Long id;
    private List<ItemPedidoDTO> itens;
    private String nomeCliente;
}

public class ItemPedidoDTO {
    private Long id;
    private String nomeProduto;
    private Integer quantidade;
}

Agora, vamos criar nossas interfaces de mapeamento com o MapStruct:

@Mapper(uses = ItemPedidoMapper.class)
public interface PedidoMapper {
    PedidoMapper INSTANCE = Mappers.getMapper(PedidoMapper.class);

    @Mapping(source = "cliente.nome", target = "nomeCliente")
    PedidoDTO toPedidoDTO(Pedido pedido);
}


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

    @Mapping(source = "produto.nome", target = "nomeProduto")
    ItemPedidoDTO toItemPedidoDTO(ItemPedido itemPedido);
}


Acredito que aqui você já entendeu que agora basta chamar o PedidoMapper, passando seu objeto Pedido, e ele irá mapear e retornar um PedidoDTO com todos os campos setados, inclusive a lista de itens do pedido.

Bem simples, né? Podemos facilmente converter entidades complexas em DTOs, mantendo nosso código limpo e de fácil entendimento. Além disso, estamos trabalhando de forma mais eficiente ao utilizar o Mappers.getMapper() para criar nossa INSTANCE, pois por baixo dos panos o MapStruct está usando uma abordagem Singleton que cria um ponto único de acesso à implementação do mapper. Ou seja, sempre será utilizada uma única instância para realizar as operações de mapeamento, proporcionando maior eficiência.

Conclusão

Espero que este conteúdo tenha sido útil para você e te ajude a tornar seu desenvolvimento em Java mais simples, limpo e eficiente. Você pode conferir também a documentação oficial, se precisar.

Quer saber mais sobre ferramentas, bibliotecas e outros assuntos relacionados ao desenvolvimento de software? Acesse a nossa página de artigos na categoria Ferramentas. Deixe seus comentários ou entre contato se precisar de algo. Até logo!


Givanilson Pereira

Sou graduado em Análise e Desenvolvimento de Sistemas e no momento estou no setor privado como Engenheiro de Software. Sou casado e pai de uma menina, aprecio uma boa comida, gosto de filmes e séries de ficção científica e um bom e velho Rock ’n’ Roll.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *