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:

Navigating Clubhouse: A Deep Dive into the Audio Social Network

Explore Clubhouse, the voice-centric social network, and discover how it reshapes our communication and social interactions.

Unlocking Mental Clarity: 7 Habits for a Sharper Mind

Discover seven effective habits to enhance mental clarity and cognitive performance, leading to better focus and productivity.

The Misconception of the Scientific Method: A Critical Analysis

A deep dive into the limitations of the scientific method and its implications on truth and knowledge.

Embracing Uniqueness: A Journey Beyond Normalcy

Discover how embracing authenticity over normalcy can lead to a fulfilling life.

Essential Reads for Aspiring Entrepreneurs: Top 20 Books

Discover the top 20 books every entrepreneur should read for success and growth.

Unlocking Entrepreneurial Success: 5 Key Mindsets to Embrace

Explore five essential mindsets that can propel your business forward and foster personal growth in the entrepreneurial landscape.

Effective Strategies for Making Production Decisions in Business

Explore how production decisions are made and the critical factors influencing these choices for business success.

Navigating the Job Hunt: 11 Tips for Motivation and Focus

Discover 11 essential strategies to stay motivated during your job search and overcome the emotional challenges of job hunting.