Ways to use MapStruct with Lombok in a Spring Boot application

In this article, you'll find code samples and instructions on how to effectively use MapStruct, Lombok, and Spring Boot.

introduce

        When you implement a service of any size, you often need to move data from one structure to another. Typically, this is the same data used at different logical layers - at the business logic, database level, or controller level for transfer to the front-end application.

        To transfer this data, you have to repeat a lot of boilerplate. really tired. I'd like to draw your attention to a library that may help you save energy. Meet MapStructure!

With this library, you can only specify structure mapping schemes. Its implementation will be collected by the library itself.

where to find it

The latest version can be found in the Maven central repository.
You can add this to your pom.xml:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.5.Final</version>
</dependency>

You need to add annotation processor in plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.5.1</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <annotationProcessorPaths>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.5.5.Final</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

If you use Gradle, you can add this to your build.gradle:

implementation "org.mapstruct:mapstruct:${mapstructVersion}"
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"

how it works

Let's take some data classes as an example:

public class CatEntity {
    private Long id;
    private String name;
    private String color;
    // getters and setters
}
 
public class CatDto {
    private Long id;
    private String name;
    private String color;
    // getters and setters
}

This is all the code we need to write for its implementation:

@Mapper
public interface CatMapper {

    CatEntity toEntity(CatDto dto);

    CatDto toDto(CatEntity entity);
}

Implementations of this interface will be created by MapStruct itself:

@Generated
public class CatMapperImpl implements CatMapper {

    @Override
    public CatEntity toEntity(CatDto dto) {
        if ( dto == null ) {
            return null;
        }

        CatEntity catEntity = new CatEntity();

        catEntity.setId( dto.getId() );
        catEntity.setName( dto.getName() );
        catEntity.setColor( dto.getColor() );

        return catEntity;
    }

    @Override
    public CatDto toDto(CatEntity entity) {
        if ( entity == null ) {
            return null;
        }

        CatDto catDto = new CatDto();

        catDto.setId( entity.getId() );
        catDto.setName( entity.getName() );
        catDto.setColor( entity.getColor() );

        return catDto;
    }
}

How to use MapStruct with Java records

In Java 14, the record class was added. MapStruct can handle them too:

public class CatEntity {
    private Long id;
    private String name;
    private String color;
    // getters and setters
}
 
public record CatRecord(
    Long id,
    String name,
    String color
) {
}

If we create a mapping interface:

@Mapper
public interface CatRecordMapper {

    CatEntity toEntity(CatRecord record);

    CatRecord toRecord(CatEntity entity);
}

Then Mapstruct will generate the following implementation:

@Generated
public class CatRecordMapperImpl implements CatRecordMapper {

    @Override
    public CatEntity toEntity(CatRecord record) {
        if ( record == null ) {
            return null;
        }

        CatEntity catEntity = new CatEntity();

        catEntity.setId( record.id() );
        catEntity.setName( record.name() );
        catEntity.setColor( record.color() );

        return catEntity;
    }

    @Override
    public CatRecord toRecord(CatEntity entity) {
        if ( entity == null ) {
            return null;
        }

        Long id = null;
        String name = null;
        String color = null;

        id = entity.getId();
        name = entity.getName();
        color = entity.getColor();

        CatRecord catRecord = new CatRecord( id, name, color );

        return catRecord;
    }
}

How to use MapStruct with Project Lombok

        In the Java world, there is a well-known large library - Project Lombok. It also reduces boilerplate code that developers have to write. More details about the library you can find on the official website.
To add this library to your project, you need to add this to pom.xml:

<dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.28</version>
        <scope>provided</scope>
</dependency>

 And, you need to add this to your annotation processor:

<annotationProcessorPaths>
    <path>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.28</version>
    </path>
</annotationProcessorPaths>

For Gradle, it's a little easier. just add it to  build.gradle

implementation 'org.projectlombok:lombok:1.18.28'
annotationProcessor "org.projectlombok:lombok:1.18.28"

And an important step has been taken! To integrate Project Lombok with MapStruct, you need to add the binding library:

annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'

For our example, @Datait is sufficient to use annotations, which add getters and setters.

@Data
public class CatDto {
    private Long id;
    private String name;
    private String color;
}

@Data
public class CatEntity {
    private Long id;
    private String name;
    private String color;
}

For the same mapper interface, it generates the following implementations:

public interface CatMapper {
    CatEntity toEntity(CatDto dto);
    CatDto toDto(CatEntity entity);
}

@Generated
public class CatMapperImpl implements CatMapper {

    @Override
    public CatEntity toEntity(CatDto dto) {
        if ( dto == null ) {
            return null;
        }

        CatEntity catEntity = new CatEntity();

        catEntity.setId( dto.getId() );
        catEntity.setName( dto.getName() );
        catEntity.setColor( dto.getColor() );

        return catEntity;
    }

    @Override
    public CatDto toDto(CatEntity entity) {
        if ( entity == null ) {
            return null;
        }

        CatDto catDto = new CatDto();

        catDto.setId( entity.getId() );
        catDto.setName( entity.getName() );
        catDto.setColor( entity.getColor() );

        return catDto;
    }
}

How to add Spring Boot logic to Mapper

Sometimes it is necessary to retrieve some fields from a Spring bean. Assuming we don't store weight information in the database, this is not accessible in Entity. Instead, we have some Spring services to provide this information.

@Data
public class CatDto {
    private Long id;
    private String name;
    private String color;
    private Integer weight;
}


@Data
public class CatEntity {
    private Long id;
    private String name;
    private String color;
}
And there is service that provides weight information:

@Service
public class CatWeightProvider {

    public Integer getWeight(String name) {
        // some logic for retrieving weight info
        return 5;
    }
}

To use this bean to retrieve weight information within the mapper interface, it should be replaced by an abstract class with methods describing any additional logic.

@Mapper(componentModel = "spring")
public abstract class CatMapper {

    @Autowired
    private CatWeightProvider provider;

    @Mapping(target = "weight", source = "entity.name", qualifiedByName = "retrieveWeight")
    public abstract CatDto toDto(CatEntity entity);

    @Named("retrieveWeight")
    protected Integer retrieveWeight(String name) {
        return provider.getWeight(name);
    }
}

In this case MapStruct will generate an implementation of this abstract class:

@Generated
@Component
public class CatMapperImpl extends CatMapper {

    @Override
    public CatDto toDto(CatEntity entity) {
        if ( entity == null ) {
            return null;
        }

        CatDto catDto = new CatDto();

        catDto.setWeight( retrieveWeight( entity.getName() ) );
        catDto.setId( entity.getId() );
        catDto.setName( entity.getName() );
        catDto.setColor( entity.getColor() );

        return catDto;
    }
}

How to ignore fields and map fields with different names

For example, a field in a database entity and a field in a dto have different names. 

For example, we need to ignore field weights in the dto layer.

@Data
public class CatDto {
    private String name;
    private String color;
    private Integer weight;
}

@Data
public class CatEntity {
    private Long idInDatabase;
    private String nameInDatabase;
    private String colorInDatabase;
}

This can be done with the following parameters:

@Mapper
public interface CatMapper {

    @Mapping(target = "weight", ignore = true)
    @Mapping(target = "name", source = "entity.nameInDatabase")
    @Mapping(target = "color", source = "entity.colorInDatabase")
    CatDto toDto(CatEntity entity);
}

Therefore, the implementation of MapStruct will be:

@Generated
public class CatMapperImpl implements CatMapper {

    @Override
    public CatDto toDto(CatEntity entity) {
        if ( entity == null ) {
            return null;
        }

        CatDto catDto = new CatDto();

        catDto.setName( entity.getNameInDatabase() );
        catDto.setColor( entity.getColorInDatabase() );

        return catDto;
    }
}

in conclusion

        We've covered the most popular scenarios that arise when developing multi-tier applications. Therefore, the combination of Project Lombok and the MapStruct library can significantly save developers time and effort on boilerplate.
Thank you for your attention!

Guess you like

Origin blog.csdn.net/qq_28245905/article/details/132128037