This is the first day of my participation in the First Challenge 2022

scenario

, usually in the back-end project development, because of the hierarchical design, such as the MVC architecture, as well as the recent very hot DDD architecture, will be at different levels, there is a corresponding DO, BO, VO, DTO, etc all kinds of POJO class, and we take a call between the hierarchy of data, usually must carry on the mapping between the object properties. For simple objects, you might use the get and set methods directly, or use the BeanUtils utility class to map attributes.

This code is often boring, tedious, and may require repeated conversions between two objects in different business processing classes. This leads to a lot of get and set conversions in the code, and if you use BeanUtils, the field names may not be consistent until runtime.

Is there a solution to this problem?

The answer is to use MapStruct, which can solve these problems elegantly.

MapStruct is a code generator component that follows the principle of convention over configuration and makes it easier to convert between our Bean objects.

Why use MapStruct?

As described in the previous article, in the multi-layer application design, there is a need to transform between different object models, attribute mapping, manual writing of these codes is not only tedious, but also prone to error, MapStruct is designed to make this work simple and automatic.

Compared to other mapping frameworks, such as BeanUtils, or Json serialization and deserialization, MapStruct generates maps at compile time, ensuring program performance and finding errors at compile time.

How do I use MapStruct?

MapStruct is essentially an annotation processor that can be integrated directly into compilation tools such as Maven or Gradle.

Using Maven as an example, we need to add the MapStruct dependency to the dependency and configure the Mapstruct-Processor in the Maven plug-in.

<properties>
    <org.mapstruct.version>1.4.2. The Final</org.mapstruct.version>
</properties>

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

<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.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>${org.mapstruct.version}</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>
Copy the code

Next, you can use MapStruct in your code.

Field mapping of the same name

For example, we now have a capability to query the Student object from the persistence layer and convert it to StudentDTO pass to the business layer. We need to convert between the Student object and the StudentDTO object.

Student.java

@Data
public class Student{
 private int no;
 private String name;
}
Copy the code

StudentDTO.java

@Data
public class StudentDTO {
 private int no;
 private String name;
}
Copy the code

For this transformation between objects, we need to create a Mapper class for mapping.

@Mapper(componentModel = "spring")
public interface StudentMapper {
    StudentDTO toDto(Student student);
}
Copy the code

Mapper: is an annotation to a MapStruct, used to create and generate an implementation of the map.

ComponentModel = “Spring” : This attribute means that the StudentMapper instance is stored as a Spring Bean object in the Spring container, so that it can be easily injected into other business code.

Because Student and StudentDTO have the same attribute name, we don’t need any additional code explicit mapping.

Different name field mapping

What if the field name is different between the DTO and PO?

Student.java

@Data
public class Student{
    private int no;
    private String name;
    private String gender;
}
Copy the code

StudentDTO.java

@Data
public class StudentDTO {
    private int no;
    private String name;
    private String sex;
}
Copy the code

As shown in the code above, the gender field names are inconsistent in Student and StudentDTO. To implement this kind of Mapping, just add the following @mapping annotation.

@Mapper(componentModel = "spring")
public interface StudentMapper {
     @Mapping(source = "gender", target = "sex")
    StudentDTO toDto(Student student);
}
Copy the code

Custom object property mapping

If each student has his own Address information, how to deal with it?

The source object class

@Data
public class Student{
    private int no;
    private String name;
    private String gender;
    private Address address;
}

@Data
public class Address{
    private String city;
    private String province;
}
Copy the code

Target object class

@Data
public class StudentDTO{
    private int no;
    private String name;
    private String sex;
    private AddressDTO address;
}

@Data
public class AddressDTO{
    private String city;
    private String province;
}
Copy the code

To map an internal object, create a Mapper for the internal object and import the Mapper to StudentMapper.

@Mapper(componentModel = "spring")
public interface AddressMapper {
    AddressDTO toDto(Address address);
}

@Mapper(componentModel = "spring",uses = {AddressMapper.class})
public interface StudentMapper {
    @Mapping(source = "gender", target = "sex")
    StudentDTO toDto(Student student);
}
Copy the code

Custom transformation logic mapping

If the object conversion is not a simple mapping between attributes, it needs to be converted according to some business logic, such as Address in each Student, and only city in the Address information in StudentDTO.

The source object class

@Data
public class Student{
    private int no;
    private String name;
    private String gender;
    private Address address;
}

@Data
public class Address{
    private String city;
    private String province;
}
Copy the code

Target object class

@Data
public class StudentDTO{
    private int no;
    private String name;
    private String sex;
    private String city;
}
Copy the code

In this case, we can either use address.city directly in the source, or we can use custom methods to accomplish the logical transformation.

@Mapper(componentModel = "spring",uses = {AddressMapper.class})
public interface StudentMapper {
    @Mapping(source = "gender", target = "sex")
    // @Mapping(source = "address.city", target = "city")
    @Mapping(source = "address", target = "city",qualifiedByName = "getAddressCity")
    StudentDTO toDto(Student student);

    @Named("getAddressCity")
    default String getChildCircuits(Address address) {
        if(address == null) {
            return "";
        }
        returnaddress.getCity(); }}Copy the code

Set the mapping

Mapping collection fields using MapStruct is also simple. List

= List

= List


@Mapper(componentModel = "spring")
public interface CourseMapper {
    CourseDTO toDto(Course port);

    List<CourseDTO> toCourseDtoList(List<Course> courses);
}

@Mapper(componentModel = "spring",uses = {CourseMapper.class})
public interface StudentMapper {
    @Mapping(source = "gender", target = "sex")
    @Mapping(source = "address", target = "city",qualifiedByName = "getAddressCity")
    CircuitDto toDto(Circuit circuit);
}
Copy the code

Other special case mappings

In addition to the common object mapping, you may need to set some fixed values during the conversion. For example, StudentDTO has the degree field, but the data has not been recorded, so you need to set the default value “unknown”. This can be done using the @mapping annotation.

@mapping (target = "degree", constant = "unknown ")
Copy the code

@ BeforeMapping and @ AfterMapping

@beforeMapping and @AfterMapping are two important annotations that you can almost guess by their names and can use to do some processing before and after the transformation.

For example, if we want to do some data validation, collection initialization, etc. before converting, we can use @beforeMapping;

After the conversion is complete, you want to modify the data result. For example, set the Boolean value of haveCourse based on the number of electives List

in the StudentDTO field.

@Mapper(componentModel = "spring",uses = {PortMapper.class})
public interface StudentMapper {
    @BeforeMapping
    default void setCourses(Student student) {
        if(student.getCourses() == null){
            student.setCourses(newArrayList<Course>()); }}@Mapping(source = "gender", target = "sex")
    StudentDTO toDto(Student student);

    @AfterMapping
    default void setHaveCourse(StudentDTO studentDto) {
        if(studentDto.getCourses()! =null && studentDto.getCourses() >0){
            studentDto.setHaveCourse(true); }}}Copy the code

conclusion

In this paper, we introduce how to use MapStruct to implement the elegant object attribute mapping, reduce our code get,set code, avoid the error of mapping between object attributes. As you can see from the sample code above, MapStruct makes extensive use of annotations, making it easy to map objects.

If you want to learn more about MapStruct, you can continue to read the official documentation. MapStruct official document

Thank you for reading, if it helps you, you can give small black dot a thumbs-up 👍, writing is not easy, need a little positive feedback. 🙏

I am xiao Hei, a programmer in the Internet “casual”

Water does not compete, you are in the flow