1. The pain points

The emergence of a framework to solve a pain point, I think the following this inconvenient operation is often written. Suppose Car is a database mapping class:

 package cn.felord.mapstruct.entity;
 
 import lombok.Data;
 
 /**
  * Car
  *
  * @author Felordcn
  * @since 13:35 2019/10/12
  **/
 @Data
 public class Car {
     private String make;
     private int numberOfSeats;
     private CarType type;
 
 }Copy the code

CarType class:

 package cn.felord.mapstruct.entity;
 
 import lombok.Data;
 
 /**
  * CarType
  *
  * @author Felordcn
  * @since 13:36 2019/10/12
  **/
 @Data
 public class CarType {
     private String type;
 }Copy the code

CarDTO is a DTO class:

 package cn.felord.mapstruct.entity;
 
 import lombok.Data;
 
 /**
  * CarDTO
  *
  * @author Felordcn
  * @since 13:37 2019/10/12
  **/
 @Data
 public class CarDTO {
     private String make;
     private int seatCount;
     private String type;
 } Copy the code

We’re querying Car from the database and then we need to convert to CarDTO, and usually we’ll write a method like this to convert:

public CarDTO carToCarDTO(Car car) { CarDTO carDTO = new CarDTO(); carDTO.setMake(car.getMake()); carDTO.setSeatCount(car.getNumberOfSeats()); carDTO.setType(car.getCarType().getType()); Return carDTO; }Copy the code

It’s tedious, tasteless and untechnical. There’s a lot of conversion and nesting involved, and all we want to do is map them. Is there a universal mapping tool that can help us figure this out? Of course there are and there are many. Someone said ApacheBeanUtil.copyPropertiesCan be implemented, but poor performance and prone to exceptions, many specifications prohibit the use of this approach. Here is a comparison of several object mapping frameworks, most of the timeMapStructHighest performance. It works like thislombokMapStructBoth are implemented at compile time and are based onGetter,SetterReflection is not used, so there are generally no runtime performance problems.

Get MapStruct today and integrate it with Spring Boot 2.x. Both IDEA and Eclipse recommend that you install the MapStruct Plugin, but you don’t have to.

2. Spring Boot 2.1.9 integrates MapStruct

Import MapStruct maven dependency coordinates from Pom. XML in Spring Boot:

<dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${mapstruct.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${mapstruct.version}</version> <scope>compile</scope> </dependency> <! -- other dependencies --> </dependencies>Copy the code

​​

3. Use MapStruct

Let’s work out where we started. See how MapStruct can reduce your programming costs.

3.1 Write a transformation source-to-target mapping

Write a Car to CarDTO mapping:

package cn.felord.mapstruct.mapping; import cn.felord.mapstruct.entity.Car; import cn.felord.mapstruct.entity.CarDTO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; /** * CarMapping ** @author Felordcn * @since 14:02 2019/10/12 */ @mapper public interface CarMapping {/** * CAR_MAPPING = Mappers. GetMapper (carmapping.class); /** * Source type Target type Member variable Same type Same variable name do not write {@link org.mapstruct.Mapping} to map ** @param car the car * @return the car dto */ @Mapping(target = "type", source = "carType.type") @Mapping(target = "seatCount", source = "numberOfSeats") CarDTO carToCarDTO(Car car); }Copy the code

3.2 Explanation of MapStruct mapping method

Just a few lines of code above and it’s easy! Explain how to do it:

Start by declaring a mapping interface marked @org.mapstruct.Mapper (not to be confused with the Mybatis annotation) to indicate that this is an entity type conversion interface. Here we declare a CAR_MAPPING so that we can call CarDTO toCarDTO(Car Car) is not familiar, abstracting our transformation method like mybatis. The @org.mapstruct.Mapping annotation is used to declare the Mapping of member attributes. This annotation has two important properties:

  • sourceRepresents the source of the transformation. Here is theCar
  • targetRepresents the target of the transformation. Here is theCarDTO

This is based on the parameter name of the member variable, so if we have a nested like Car that has a member variable of type CarType in it, and its type property maps to the type string in CarDTO, we use type.type to get the value of the property. If you have multiple layers and so on. MapStruct ends up calling setter and getter methods, not reflection. This is one of the reasons why it performs better. NumberOfSeats maps to seatCount. Did we forget one attribute make that can be omitted because they are in exactly the same position and have exactly the same name? And for the packaging class is automatic unpacking and sealing operation, and is thread safe. These are not the only features of MapStruct, but there are other complex features as well:

Set conversion defaults and constants. When the target value is null we can set the default value. Note that these are primitive types and correspond to boxing types, as follows

  @Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")Copy the code

Note that constants do not refer to sources (source attributes cannot be specified). Here is the correct action:

  @Mapping(target = "stringConstant", constant = "Constant Value")Copy the code

3.2 compile Mapper

When your application is compiled. You’ll notice that maven is a subdirectory of targetgenerated- SourcesAnnotations, and you’ll see that an implementation class is generated for CarMappingImpl:

package cn.felord.mapstruct.mapping; import cn.felord.mapstruct.entity.Car; import cn.felord.mapstruct.entity.CarDTO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; import javax.annotation.Generated; import org.springframework.stereotype.Component; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2019-10-12T15:05:36+0800", comments = "version: 1.3.0. Final, the compiler: javac, environment: Java 1.8.0_222 (Amazon.com Inc.)") @Component public implements CarMappingImpl {@Override public CarDTO carToCarDTO(Car car) { if ( car == null ) { return null; } CarDTO carDTO = new CarDTO(); carDTO.setType( carCarTypeType( car ) ); carDTO.setSeatCount( car.getNumberOfSeats() ); carDTO.setMake( car.getMake() ); return carDTO; } private String carCarTypeType(Car car) { if ( car == null ) { return null; } CarType carType = car.getCarType(); if ( carType == null ) { return null; } String type = carType.getType(); if ( type == null ) { return null; } return type; }}Copy the code

4. MapStruct advanced operation

The following are several advanced operations on MapStruct:

4.1 Formatting Operations

Formatting is also something we often use, such as number formatting and date formatting. This is a number formatting operation that follows the java.text.DecimalFormat specification:

       @Mapping(source = "price", numberFormat = "$#.00")Copy the code


The following shows a formatting operation that maps a set of dates to a set of date strings:


   @IterableMapping(dateFormat = "dd.MM.yyyy")
   List<String> stringListToDateList(List<Date> dates);Copy the code

4.2 Using Java Expressions

The following shows how to inject LocalDateTime as the current time value into the addTime property.

Start by importing LocalDateTime in @org.mapstruct.Mapper’s imports property, which is an array meaning you can import as many processing classes as you need:

  @Mapper(imports = {LocalDateTime.class})Copy the code

All you need to do is add the annotation @org.mapstruct.Mapping to the corresponding method, whose attribute expression receives an expression that Java () includes:

  • No input version:
  @Mapping(target = "addTime", expression = "java(LocalDateTime.now())")Copy the code


  • Carry in the version of the parameter we willCarFactory date string ofmanufactureDateStrInjected into theCarDTOLocalDateTimeThe type attributeaddTimeTo:
   @Mapping(target = "addTime", expression = "java(LocalDateTime.parse(car.manufactureDateStr))")
   CarDTO carToCarDTO(Car car);Copy the code


Mapper is injected into the Spring IoC container

If you want to inject Mapper into the Spring IoC container, you just need to declare this. Instead of building a singleton, you can reference CarMapping just like any other Spring bean:

package cn.felord.mapstruct.mapping; import cn.felord.mapstruct.entity.Car; import cn.felord.mapstruct.entity.CarDTO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; /** * @author Felordcn * @since 14 :02 2019/10/12 */ @mapper (componentModel = "Spring ") Public interface CarMapping {/** * used to call instances of actual development can use Spring injection */ // CarMapping CAR_MAPPING = Mappers.getMapper(CarMapping.class); /** * Source type Target type Member variable Same type Same variable name do not write {@link Mapping} to map ** @param car the car * @return the car dto */ @mapping (target = "type", source = "carType.type") @Mapping(target = "seatCount", source = "numberOfSeats") CarDTO carToCarDTO(Car car); }Copy the code

​​

5. To summarize

There’s a lot more to MapStruct. But for readability, I recommend using the above easy-to-understand features. You can go to mapstruct.org if you’re interested. Working with Lombok and jSR303, which I introduced, allows you to be more business focused and code cleaner.

Follow our public id: Felordcn for more information

Personal blog: https://felord.cn