[Recommended] Use mapstruct to convert PO VO DTO

This article is reprinted, original address: Stop using BeanUtils, isn’t this PO VO DTO conversion artifact great?

Have you ever had a headache writing some original code for entity conversion, especially when there are a lot of entity fields? Introducing an open source project mapstruct that can be easily and elegantly converted to simplify your code. Of course, some people like to write get set, or use BeanUtils to copy. Code is just a tool, and this article only provides an idea.

First post the official website address: https://mapstruct.org/

Without further ado, here’s the code:

pom configuration:

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<org.mapstruct.version>1.4.1.Final</org.mapstruct.version>
<org.projectlombok.version>1.18.12</org.projectlombok.version>
</properties>


<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>

<!-- lombok dependencies should not end up on classpath -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
<scope>provided</scope>
</dependency>
\t
<!-- Versions before idea 2018.1.1 need to add the following configuration. Later versions do not need it and can be commented out. I use 2019.3 myself -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
<scope>provided</scope>
</dependency>

</dependencies>


<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

A few words about the version compatibility of lombok and mapstruct. The maven plug-in must use version 3.6.0 or above, and lombok should use version 1.16.16 or above. In addition, don’t forget to add the compiled lombok mapstruct plug-in. Otherwise, the following error will appear: No property named “aaa” exists in source parameter(s). Did you mean “null”?

This exception is caused by the lack of get setter method due to lombok compilation exception. Also, missing a constructor will also throw an exception.

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    private String name;
    private int age;
    private GenderEnum gender;
    private Double height;
    private Date birthday;

}


public enum GenderEnum {
    Male("1", "male"),
    Female("0", "female");

    private String code;
    private String name;

    public String getCode() {
        return this.code;
    }

    public String getName() {
        return this.name;
    }

    GenderEnum(String code, String name) {
        this.code = code;
        this.name = name;
    }
}


@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StudentVO {
    private String name;
    private int age;
    private String gender;
    private Double height;
    private String birthday;
}


@Mapper
public interface StudentMapper {

    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mapping(source = "gender.name", target = "gender")
    @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    StudentVO student2StudentVO(Student student);

}

Entity classes are indispensable in the development process. Even if they are generated with tools, they must be present. The part that needs to be handwritten is the interface of this Mapper. After compilation is completed, the corresponding implementation class will be automatically generated.

Then you can directly use mapper to convert entities.

public class Test {

    public static void main(String[] args) {

        Student student = Student.builder().name("Xiao Ming").age(6).gender(GenderEnum.Male).height(121.1).birthday(new Date()).build();
        System.out.println(student);
        //This line of code is the actual code to be used
        StudentVO studentVO = StudentMapper.INSTANCE.student2StudentVO(student);
        System.out.println(studentVO);

    }

}

The mapper can perform field mapping, change field types, and specify formatting methods, including some default processing of dates.

You can manually specify the formatting method:

@Mapper
public interface StudentMapper {

    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mapping(source = "gender", target = "gender")
    @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    StudentVO student2StudentVO(Student student);

    default String getGenderName(GenderEnum gender) {
        return gender.getName();
    }

}

The above is just the simplest entity mapping processing. Here are some advanced usages:

1. List conversion

Attribute mapping is based on the above mapping configuration

@Mapper
public interface StudentMapper {

    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mapping(source = "gender.name", target = "gender")
    @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    StudentVO student2StudentVO(Student student);


    List<StudentVO> students2StudentVOs(List<Student> studentList);

}


public static void main(String[] args) {

    Student student = Student.builder().name("Xiao Ming").age(6).gender(GenderEnum.Male).height(121.1).birthday(new Date()).build();

    List<Student> list = new ArrayList<>();
    list.add(student);
    List<StudentVO> result = StudentMapper.INSTANCE.students2StudentVOs(list);
    System.out.println(result);
}

2. Convert multiple objects to one object

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    private String name;
    private int age;
    private GenderEnum gender;
    private Double height;
    private Date birthday;

}


@Data
@AllArgsConstructor
@Builder
@NoArgsConstructor
public class Course {

    private String courseName;
    private int sortNo;
    private long id;

}


@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StudentVO {
    private String name;
    private int age;
    private String gender;
    private Double height;
    private String birthday;
    private String course;
}


@Mapper
public interface StudentMapper {

    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mapping(source = "student.gender.name", target = "gender")
    @Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "course.courseName", target = "course")
    StudentVO studentAndCourse2StudentVO(Student student, Course course);

}


public class Test {

    public static void main(String[] args) {

        Student student = Student.builder().name("Xiao Ming").age(6).gender(GenderEnum.Male).height(121.1).birthday(new Date()).build();
        Course course = Course.builder().id(1L).courseName("中文").build();

        StudentVO studentVO = StudentMapper.INSTANCE.studentAndCourse2StudentVO(student, course);
        System.out.println(studentVO);
    }

}

3. Default value

@Mapper
public interface StudentMapper {

    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mapping(source = "student.gender.name", target = "gender")
    @Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "course.courseName", target = "course")
    @Mapping(target = "name", source = "student.name", defaultValue = "Zhang San")
    StudentVO studentAndCourse2StudentVO(Student student, Course course);

}

Expand knowledge points: @Mapper primary key has different attribute values. You can see what the functional uses of its different attribute values are.

Commonly used attributes are: builder, uses, unmappedTargetPolicy, unmappedSourcePolicy, and many other attributes.

4. Reference articles

1. Pitfalls of using lombok@Builder in Mapstruct

2. Introduction and summary of the use of Mapstruct @Mapper @Mapping

3. Advanced use of MapStruct<2>