MapStruct con Spring Boot

mapstruct

En este artículo vamos a ver como usar MapStruct con Spring Boot, el cual es un simple Mapper para convertir de un objeto a otro.

El API de MapStruct contiene las funcionalidades necesarias para convertir entre dos Bean de Java.

¿Por qué usar MapStruct?

MapStruct lo usaremos siempre que queramos hacer algún tipo de conversión entre objetos de Java, por ejemplo, si tenemos un DTO y queremos transformarlo en una Entidad.

Además MapStruct nos facilita su uso con tan solo crear una interfaz y definir los métodos de conversión.

¿Cómo usar MapStruct?

Vamos a comenzar añadiendo su dependencia Maven, hay que tener en cuenta que nosotros usamos Lombok por lo que también lo incluiremos:

Maven

    <dependency>
      <groupId>org.mapstruct</groupId>
      <artifactId>mapstruct</artifactId>
      <version>${mapstruct.version}</version>
    </dependency>
    <dependency>
      <groupId>org.mapstruct</groupId>
      <artifactId>mapstruct-processor</artifactId>
      <version>${mapstruct.version}</version>
    </dependency>
<plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven-compiler-plugin.version}</version>
        <configuration>
          <release>${java.version}</release>
          <annotationProcessorPaths>
            <path>
              <groupId>org.mapstruct</groupId>
              <artifactId>mapstruct-processor</artifactId>
              <version>${mapstruct.version}</version>
            </path>
            <path>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
              <version>${lombok.version}</version>
            </path>
          </annotationProcessorPaths>
        </configuration>
      </plugin>

Conversión básica

Vamos a empezar con el caso más básico de mapper

@Getter
@Setter
public class CarEntity {
    private String color;
    private String model;
}
 
@Getter
@Setter
public class CarDto {
    private String color;
    private String model;
}

Vamos a crear la interfaz que se encargará de mapstruct:

@Mapper(componentModel = "spring")
public interface CarMapper {
    CarEntity toEntity(CarDto source);
    CarDto toDto(CarEntity target);
}

Para poder hacer uso de Spring IoC, tenemos que añadir el componentModel con la etiqueta «spring«.

La interfaz que hemos creado, realizará una implementación para los métodos creados.

Conversión con diferentes campos

Hay veces que tendremos diferentes nombre en los campos entre los dos objetos para ello usaremos una nueva propiedad de mapstruct @Mapping

@Getter
@Setter
public class CarEntity {
    private String colorCode;
    private String name;
}
 
@Getter
@Setter
public class CarDto {
    private String color;
    private String model;
}

La nueva interfaz hacer uso de @Mapping para mapear los campos:

@Mapper
public interface CarMapper {
    @Mappings({
      @Mapping(target="color", source="colorCode"),
      @Mapping(target="model", source="name")
    })
    CarDto Dto(CarEntity entity);
    @Mappings({
      @Mapping(target="colorCode", source="color"),
      @Mapping(target="model", source="name")
    })
    CarEntity toEntity(CarDto dto);
}

Conversión con referencia a otros objetos

En algunas ocasiones tendremos que convertir objetos con objetos dentro, vamos a verlo con un ejemplo:

@Getter
@Setter
public class CarEntity {
    private String color;
    private String model;
   private UserEntity user;
}
 
@Getter
@Setter
public class CarDto {
    private String color;
    private String model;
    private UserDto user;
}

@Getter
@Setter
public class UserEntity {
    private String name;

}
 
@Getter
@Setter
public class UserDto {
    private String name;
}

Vamos a crear un nuevo mapper y el mapper padre lo referenciará:

@Mapper(componentModel = "spring")
public interface UserMapper {
    UserEntity toEntity(UserDto source);
    UserDto toDto(UserEntity target);
}


@Mapper(componentModel = "spring", uses = UserMapper.class))
public interface CarMapper {
    CarEntity toEntity(CarDto source);
    CarDto toDto(CarEntity target);
}

Como vemos en el ejemplo anterior hemos añadido en el mapper padre la sentencia uses = UserMapper.class para que pueda mapear las clases del tipo User.

Conversión de Tipos con MapStruct

MapStruct nos ofrece conversión implicita de tipos, por lo general la más usada suele ser la conversión de un string a tipo date, vamos a ver lo mejor con un ejemplo:

@Getter
@Setter
public class CarEntity {
    private String colorCode;
    private String name;
    private String date;
}
 
@Getter
@Setter
public class CarDto {
    private String color;
    private String model;
    private LocalDate date;
}

Modificamos nuestra interfaz para incorporar esta conversión de tipos:

@Mapper(componentModel = "spring")
public interface CarMapper {
    @Mappings({
      @Mapping(target="color", source="colorCode"),
      @Mapping(target="model", source="name"),
      @Mapping(target="date", source = "date",
           dateFormat = "dd-MM-yyyy")
    })
    CarDto Dto(CarEntity entity);
    @Mappings({
      @Mapping(target="colorCode", source="color"),
      @Mapping(target="model", source="name"),
     @Mapping(target="date", source="date",
           dateFormat="dd-MM-yyyy")
    })
    CarEntity toEntity(CarDto dto);
}

Ignorar un campo con MapStruct

Hay veces que necesitamos ignorar un campo, para ello vamos a usar la propiedad ignore.

@Getter
@Setter
public class CarEntity {
    private String color;
    private String model;
   private boolean valueToIgnore;
}
 
@Getter
@Setter
public class CarDto {
    private String color;
    private String model;
   private boolean valueToIgnore;

}
@Mapper(componentModel = "spring")
public interface CarMapper {
    @Mapping(ignore = true, target = "valueToIgnore")
    CarEntity toEntity(CarDto source);

    @Mapping(ignore = true, target = "valueToIgnore")
    CarDto toDto(CarEntity target);
}

En el ejemplo anterior lo que hemos logrado es que el campo valueToIgnore, sea ignorado tanto al convertir a entidad como al convertir a dto.

Anotaciones BeforeMapping y AfterMapping en Mapstruct

En muchas ocasiones vamos a necesitar realizar algún tipo de mapeo antes o después de la conversión, para estos casos hacemos uso de las anotaciones @BeforeMappging y @AfterMapping, por ejemplo en función de un tipo de objeto, popular un campo.

@Getter
@Setter
public class CarEntity {
    private String colorCode;
    private String name;
}

public class PickUpCar extends Car {
}
public class SedanCar extends Car {
} 

@Getter
@Setter
public class CarDto {
    private String color;
    private String model;
    private CarType type;
}

public enum CarType {
    PICKUP, SEDAN
}
@Mapper(componentModel = "spring")
public interface CarMapper {
    
    CarEntity toEntity(CarDto dto);
    CarDto Dto(CarEntity entity);

    @BeforeMapping
    default void setTypeCar(Car car, @MappingTarget CarDTO carDto) {
        if (car instanceof SedanCar) {
            carDto.setType(CarType.SEDAN);
        }
        if (car instanceof PickupCar) { 
            carDto.setType(CarType.PICKUP);
        }
    }
 
    @AfterMapping
    default void changeColor(@MappingTarget CarDTO carDto) {
        carDto.setColor("yellow");
    }
 }

Conclusión

En esta entrada hemos visto como usar MapStruct con Spring Boot, viendo algunos ejemplos. Aunque hay otras librerías que nos van ayudar también para realizar un mapper entre nuestros beans, quizás sea este el que mejor se adapta a Spring facilitando muchísimo el trabajo y el tiempo de desarrollo.

Otros artículos que te pueden interesar

Spring Boot AOP

Cómo funciona la inyección de dependencias en Spring

Cómo funciona BeanPostProcessor en Spring


Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *