preface

MVC mode is the standard development mode of mainstream projects at present. In this mode, the framework has a clear hierarchical structure, which is mainly divided into Controller, Service and Dao. Under the hierarchical structure, the data transmission requirements between different layers will be different. We cannot use one object to run through three layers, which is not in accordance with the development specification and not flexible enough.

For example, a field in the database is dateTime. This timestamp is stored in the database as 2020-11-06 23:59:59.999999, but when passed to the front-end, the interface is required to return yyyY-MM-DD. Or some data in the database is a comma-concatenated String, but the front end needs to be a sliced List, etc.

So we propose the object model between the levels, which is the VO, DTO, DO, PO and so on. This way of differentiating hierarchical object models clarified our object passing between levels, but the conversion and copying of values between object models was tedious, repetitive and tedious, and writing such mapping code was a tedious and error-prone task.

The simplest and most crude way to copy objects is to keep creating new objects and then setters and getters between objects. This is fine with a few field properties. If there are many field properties, the code for a large set and get will look ugly. Therefore, we need to use object copy tools. At present, there are many such as BeanCopy, Dozer and so on in the market, but these are not good enough. Today, I recommend an entity mapping tool is MapStruct.

introduce

MapStruct is a fast and secure bean mapping code generator that can transform properties between objects with simple annotations. MapStruct is an Apache LICENSE 2.0 open source product. Making the source address is https://github.com/mapstruct.


Through the three questions (What, Why and How) on the official website, we can have a general understanding of the role of MapStruct, its advantages and How it is implemented.


From the above three questions we can get the following information:

  • MapStruct, a convention over configuration approach, greatly simplifies the implementation of mappings between Java bean types, working with simple annotations. The generated mapping code uses normal method calls instead of reflection, making it fast, type-safe, and easy to understand.

  • MapStruct generates Bean maps at compile time compared to other mapping frameworks, which ensures high performance and quick feedback and thorough error checking for developers.

  • MapStruct is an annotation processor that is plugged into the Java compiler and can be used for command-line builds (Maven, Gradle, etc.) as well as in your preferred IDE (IDEA, Eclipse, etc.).

The code

MapStruct requires Java 1.8 or higher. For Maven-based projects, add the following dependencies to the POM file

<! -- Specify version -->
<properties>
    <org.mapstruct.version>1.4.1. The Final</org.mapstruct.version>
</properties>
<! Add dependencies -->
<dependencies>  <dependency>  <groupId>org.mapstruct</groupId>  <artifactId>mapstruct</artifactId>  <version>${org.mapstruct.version}</version>  </dependency>  <dependency>  <groupId>org.mapstruct</groupId>  <artifactId>mapstruct-processor</artifactId>  <version>${org.mapstruct.version}</version>  </dependency> </dependencies> Copy the code

After introducing the basic dependency, we can write the code, simply define a mapping class, in order to distinguish from the Mapper interface in Mybatis, we can call it xxObjectConverter.

For example, the car object mapping class is named CarObjectConverter. We have two object models DO and DTO, and their internal property fields are as follows:

The database corresponds to the persistent object model CarDo

public class Car {
    @ApiModelProperty(value = "The primary key id")
    private Long id;
 
    @ApiModelProperty(value = "Manufacturer")
 private String manufacturers;   @ApiModelProperty(value = "Sales channel")  private String saleChannel;   @ApiModelProperty(value = "Production date")  private Date productionDate; .} Copy the code

An object model for inter-hierarchy transport, CarDto

public class CarDto {
    @ApiModelProperty(value = "The primary key id")
    private Long id;
 
    @ApiModelProperty(value = "Manufacturer")
 private String maker;   @ApiModelProperty(value = "Sales channel")  private List<Integer> saleChannel;   @ApiModelProperty(value = "Production date")  private Date productionDate; .} Copy the code

Then write the concrete MapStruct object mapper

@Mapper
public interface CarObjectConverter{

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

 @Mapping(target = "maker", source = "manufacturers")  CarDto carToCarDto(Car car);  } Copy the code

For fields with the same name, it is not necessary to specify additional mapping rules, but for attributes with different field names, it is necessary to specify the mapping rules of the fields. As shown above, the field name of the manufacturer of the persistence layer DO is Manufacturers, while the field name of the DTO model for inter-hierarchy transmission is Maker. We need to point out the Mapping rule through @mapping annotation in the Mapping method. I like to write target in the front and source in the back, so as to keep consistent with the position of the Mapping object. When there are many different fields, it is convenient for comparison and not easy to confuse.

It is also common to encounter date-format conversions during development, as described in the beginning, where you can also specify mapping rules for dates

@Mapper
public interface CarObjectConverter{

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

 @Mapping(target = "maker", source = "manufacturers")  @Mapping(target = "productionDate", dateFormat = "yyyy-MM-dd", source = "productionDate")  CarDto carToCarDto(Car car);  } Copy the code

These are just simple field mappings, but sometimes we have different types of fields between our two object models, like saleChannel for car sales channel, which in the database is a comma concatenation of string values 1,2,3, and we want to pass in the Integer type of List, How do you map this complexity?

There are methods, too. Let’s write a utility method that separates a string from a comma and then converts it to a List, as follows

public class CollectionUtils {

    public static List<Integer> list2String(String str) {
        if (StringUtils.isNoneBlank(str)) {
            return Arrays.asList(str.split(",")).stream().map(s -> Integer.valueOf(s.trim())).collect(Collectors.toList());
 }  return null;  } } Copy the code

Then use the expression in the Mapping Mapping

@Mapper
public interface CarObjectConverter {

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

 @Mapping(target = "maker", source = "manufacturers")  @Mapping(target = "productionDate", dateFormat = "yyyy-MM-dd", source = "productionDate")  @Mapping(target = "saleChannel", expression = "java(com.jiajian.demo.utils.CollectionUtils.list2String(car.getSaleChannel()))")  CarDto carToCarDto(Car car);  } Copy the code

This completes the mapping of all fields, which we can call as follows where we need the object model transformation

CarDto carDto = CarObjectConverter.INSTANCE.carToCarDto(car);
Copy the code

So this is Copy between individual objects and a lot of times we need to convert between List object models, so we’ll just write the method carToCarDtos

@Mapper
public interface CarObjectConverter{

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

 @Mapping(target = "maker", source = "manufacturers")  @Mapping(target = "productionDate", dateFormat = "yyyy-MM-dd", source = "productionDate")  @Mapping(target ="saleChannel", expression = "java(com.jiajian.demo.utils.CollectionUtils.list2String(car.getSaleChannel()))")  CarDto carToCarDto(Car car);   List<CarDto> carToCarDtos(List<Car> carList);  } Copy the code

know

If you’re wondering how this works, we just create an interface and then add an annotation to the interface method and specify the mapping rules for the fields in the annotation to copy object properties. How does this work?

What we’re creating here with MapStruct is just an interface, and to implement a specific functional interface you have to implement it.

MapStruct creates an implementation class for us when our code is compiled, and this implementation class uses setter and getter methods for field assignment to implement object mapping.


One thing to note here: if you modify any of the mapping objects, you need to run MVN clean before starting the project, otherwise an error will be reported during debugging.

At the end

MapStrut does a lot more than this, but I’ve just picked out a few examples of the syntax that are commonly used. If you want to know more about MapStrut, you can refer to the official Reference Guide.

After I met MapStruct, I started to get rid of the original BeanCopyUtils tools in the project. MapStruct is much simpler and easier to use and has a lot of customization.

MapStruct is the simplest, safest and most efficient way to copy property values using setters and getters. But MapStruct helps us implement it better, avoiding redundant and repeated code in the project, the road to simplicity.

This article is formatted using MDNICE