rhondamuse.com

Comprehensive Overview of MapStruct with Spring Boot and Lombok

Written on

MapStruct illustration related to Spring Boot

Background

MapStruct is a Java library designed for generating code that maps various Java objects, such as DTOs and entities, utilizing annotations. The author has implemented MapStruct in a Spring Boot application, integrating it with Vavr and Lombok, while managing dependencies through Maven.

Setup

For Maven projects, the necessary configuration should be included in the pom.xml file:

<dependencyManagement>

<dependency>

<groupId>org.mapstruct</groupId>

<artifactId>mapstruct</artifactId>

<version>${mapstruct.version}</version>

</dependency>

...

</dependencyManagement>

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<version>3.10.1</version>

<configuration>

...

<annotationProcessorPaths combine.children="append">

<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>

<path>

<groupId>org.projectlombok</groupId>

<artifactId>lombok-mapstruct-binding</artifactId>

<version>${lombok-mapstruct-bindings.version}</version>

</path>

</annotationProcessorPaths>

</configuration>

</plugin>

Refer to the MapStruct documentation for available versions, and find Lombok-mapstruct-binding versions as needed.

Mapper

Since this example utilizes Spring Boot, we will employ the annotation that enables the mapper to be created as a bean:

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)

public interface MyMapper {

}

Simple Example

Assuming we have a DTO and an entity where all properties share the same name and type, creating a mapper is straightforward:

@Builder

public class Car {

String color;

int amountOfSeats;

int maxSpeed;

}

@Builder

public class CarEntity {

String color;

int amountOfSeats;

int maxSpeed;

}

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)

public interface MyMapper {

CarEntity toEntity(Car car);

Car fromEntity(CarEntity entity);

}

MapStruct will utilize the builders generated by Lombok to create the objects.

Handling Different Field Names

In cases where the DTO and entity contain fields of the same type but with different names, you can use @Mapping(source = "sourceField", target = "targetField") to specify the mapping:

@Builder

public class Car {

String color;

int amountOfSeats;

int maxSpeed;

}

@Builder

public class CarEntity {

String color;

int numberOfSeats;

int maximumSpeed;

}

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)

public interface MyMapper {

@Mapping(source = "amountOfSeats", target = "numberOfSeats")

@Mapping(source = "maxSpeed", target = "maximumSpeed")

CarEntity toEntity(Car car);

@InheritInverseConfiguration

Car fromEntity(CarEntity entity);

}

Introducing Vavr Options

To enable MapStruct to map between different types, define a default method for the conversion. Ensure that the source and target names match or specify them using @Mapping.

@Builder

public class Car {

Option<String> color;

int amountOfSeats;

Option<Integer> maxSpeed;

}

@Builder

public class CarEntity {

String color;

int amountOfSeats;

int maximumSpeed;

}

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)

public interface MyMapper {

@Mapping(source = "maxSpeed", target = "maximumSpeed")

CarEntity toEntity(Car car);

@InheritInverseConfiguration

Car fromEntity(CarEntity entity);

default <T> T fromOption(Option<T> value) {

return value.getOrNull();

}

default <T> Option<T> toOption(T value) {

return Option.of(value);

}

}

If the object and its encapsulated option are of different types, a specific implementation must be provided.

Mapping Between Java and Vavr Collections

Similar to handling options, add the following methods to facilitate mapping between lists:

default <T> List<T> fromJavaList(java.util.List<T> javaList) {

return Option.of(javaList)

.map(List::ofAll)

.getOrNull();

}

default <T> java.util.List<T> toJavaList(List<T> vavrList) {

return Option.of(vavrList)

.map(List::toJavaList)

.getOrNull();

}

For sets, simply replace List with Set in the above examples.

Common Mapping Functions

If Vavr is used throughout your project, consider defining common default methods in a single MapperHelper interface to avoid repetition in every mapper. Include it using:

@Mapper(

componentModel = MappingConstants.ComponentModel.SPRING,

uses = MapperHelper.class

)

public interface MyMapper {

}

Alternatively, you can define the default methods in a simple interface and extend the helper interface in your mapper.

Non-Direct Mapping with Named Default Methods

To execute specific logic during mapping, such as converting a null string to an empty one, you can use @Named("name") with qualifiedByName:

@Builder

public class Car {

String color;

int amountOfSeats;

int maxSpeedMps;

}

@Builder

public class CarEntity {

String color;

int amountOfSeats;

int maxSpeedKph;

}

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)

public interface MyMapper {

@Mapping(

source = "maxSpeedMps",

target = "maxSpeedKph",

qualifiedByName = "mpsToKph"

)

CarEntity toEntity(Car car);

@Named("mpsToKph")

default int toKph(int mps) {

return mps * 3.6;

}

}

This method can be applied to any input and output types.

Setting Default Values

MapStruct allows you to define default values for target fields:

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)

public interface MyMapper {

@Mapping(

target = "color",

defaultValue = "unspecified"

)

Car fromEntity(CarEntity car);

}

Default values can also be specified for other data types, such as long or enums.

Utilizing Generated Methods in Default Methods

Generated methods can be utilized within default methods:

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)

public interface MyMapper {

Car fromEntity(CarEntity car);

default List<Car> fromEntitySet(Set<CarEntity> entities) {

return entities.map(this::fromEntity).toList();

}

}

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

The Allure of AGI: Humanity's Pursuit of Superintelligence

Exploring humanity's drive for AGI, its implications, risks, and the ethical considerations surrounding superintelligence.

Understanding the Essentials of Parallel Computing: Key Insights

Explore four vital concepts to grasp before diving into parallel computing and understand its benefits and limitations.

Mastering Variable Value Swapping in Python: Techniques Unveiled

Discover various efficient methods for swapping variable values in Python with practical examples and expert tips.

How to Begin Value Investing as a Data Scientist

A guide for data scientists on integrating data skills into value investing, highlighting techniques and strategies for effective analysis.

Exploring VeRA: A Revolutionary Approach to LoRA Efficiency

Discover VeRA, a new method that enhances LoRA's efficiency by significantly reducing trainable parameters while maintaining performance.

# Embrace Your Uniqueness: The Key to Creative Success

Discover how embracing your unique qualities can elevate your creative work and lead to genuine success.

Crafting Engaging Browser Games Using Python and Pygame

Discover how to build interactive browser games with Python and Pygame, while revisiting nostalgic classics.

Embrace the Unexpected: Transforming Interactions for Impact

Discover how embracing unpredictability can enhance connections and leave lasting impressions.