Efficient and elegant object copy MapStruct entry to proficiency, actual combat version

I. Introduction

In the development, the most troublesome thing is: the copy between objects, the VO of the front end is inconsistent with the Entity of the database!

The best performance is manual set, which is mainly boring and untechnical, which not only consumes a lot of time but is also prone to errors;

Therefore, if we want to become excellent programmers, we need to use more wheels, so that development efficiency can be multiplied with half the effort, and development skills have also increased a lot!

If there is no requirement for system performance, no matter how it is implemented, it is good, but we must pursue it, and pursue high quality!

Everything has its own value, so don't push it or step on it!

2. Introduction to MapStruct

MapStruct is JSR 269a Java-based annotation processor for generating type-safe Bean mapping classes.

All you have to do is define a mapper interface that declares any desired mapping methods. During compilation, MapStruct将生成此接口的实现. This implementation uses pure Java method calls to map between source and target objects, ie 无反射or something similar.

Compared to writing mapping code manually, MapStruct achieves this by generating code that is tedious and error-prone 节省时间. Follow 配置方法的约定, MapStruct uses sensible defaults, but can go astray when configuring or implementing special behavior.

insert image description here

3. Advantages

Compared with dynamic mapping frameworks, MapStruct has the following advantages:

  • 不是反射Fast execution by using normal method calls

  • 编译时类型安全: You can only map objects and properties that map to each other, you won't accidentally map an order entity to a customer DTO, etc.

  • Clear error reporting at build time if

    • Incomplete mapping (not all target properties are mapped)

    • Incorrect mapping (could not find correct mapping method or type conversion)

You can take a look at the performance graph:
insert image description here

4. Combat integration

0. use

@MapperMarking an interface as a mapping interface
For properties with different names in the source and target objects, annotations can be used to configure the names: @Mapping
By convention, an interface declares a member Mappers INSTANCEthat provides clients with access to the mapper implementation.
Let's use it in detail!

1. Import dependencies

The latest one is used here. If lombok is introduced, there may be problems, that is, both of them run during the compilation period. If mapstruct is executed before lombok, the get and set methods will not be found, so there will be problems. The official website already has Got a solution! Now it will start without error!

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.3.Final</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>

2. Error Summary

  1. The impl implementation class will not be automatically generated?

We need to add dependencies:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.3.Final</version>
</dependency>
  1. After restarting, there will be a conflict with lombok:
java: No property named "name" exists in source parameter(s).
 Type "UserVO" has no properties.

Official website solution article address

<build>
  <pluginManagement>
      <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.mapstruct</groupId>
                          <artifactId>mapstruct-processor</artifactId>
                          <version>1.5.3.Final</version>
                      </path>
                      <path>
                          <groupId>org.projectlombok</groupId>
                          <artifactId>lombok</artifactId>
                          <version>1.18.24</version>
                      </path>
                      <path>
                          <groupId>org.projectlombok</groupId>
                          <artifactId>lombok-mapstruct-binding</artifactId>
                          <version>0.2.0</version>
                      </path>
                  </annotationProcessorPaths>
              </configuration>
          </plugin>
      </plugins>
  </pluginManagement>
</build>

3. Common practice 1

user table:

@Data
public class User {
    
    
    
    private Integer id;
    
    private String username;
    
    private Integer age;
}

Front-end user VO:

@Data
public class UserVO {
    
    

    private Integer id;

    private String name;

    private Integer age;
}

We create interface for mapping between two objects:

import com.example.demo.mapstruct.entity.User;
import com.example.demo.mapstruct.entity.UserVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

/**
 * @author wangzhenjun
 * @date 2023/1/28 16:05
 */
@Mapper
public interface UserMapper {
    
    

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

    @Mapping(source ="name",target = "username")
    User userVOToUser(UserVO userVO);

}

More attributes can be nested:

@Mappings({
    
    
        @Mapping(source ="name",target = "username"),
        @Mapping(source ="name1",target = "username1")
})

You can also:

@Mapping(source ="name",target = "username")
@Mapping(source ="name1",target = "username1")

Write a test class:

@SpringBootTest
class DemoApplicationTests {
    
    

    @Test
    void demoMapstruct(){
    
    
        UserVO userVO = new UserVO(1,"小红",18);
        User user = UserMapper.INSTANCE.userVOToUser(userVO);
        System.out.println(user);

    }

}

insert image description here
We see no problems with the copy!

Let's see how it works:

mapstructThe implementation class will be automatically generated at compile time to help us assign values, the default strategy is not specified, and the name is consistent for copying!
Inconsistency can be specified as above, if not specified, there will be no set method!

insert image description here

4. Common practice 2

The following demonstrates the mapping method of multiple source parameters:

We add a field to the user class:

private BigDecimal score;

Add a new Scoreclass:

@Data
@AllArgsConstructor
public class Score {
    
    

    private Integer studentId;

    private BigDecimal score;
}

Adjust the interface above UserMapper:

@Mappings({
    
    
       @Mapping(source ="userVO.name",target = "username"),
       @Mapping(source ="score.score",target = "score")
})
User userVOToUser(UserVO userVO, Score score);

Test code:

UserVO userVO = new UserVO(1,"小红",18);
Score score = new Score(1,new BigDecimal(100));
User user = UserMapper.INSTANCE.userVOToUser(userVO,score);
System.out.println(user);

The result shows normal:

insert image description here

5. Common practice 3

Let's look at a custom method that can be used at the enterprise level, and then assign it:

Scenario: A product has length, width and height, we change the length, width and height from cm to m!

There are also many conversions from String to Integer, Float, etc., all of which are implemented according to the following custom method!

VO and object are the same!

@Data
@AllArgsConstructor
public class ProductVO {
    
    

    private Integer id;

    private String name;

    private BigDecimal length;

    private BigDecimal width;

    private BigDecimal high;

}

Look clearly, don't mislead the package!
qualifiedByName: Specify the name of the custom method
@Named("cmToM"): an alias, if you don't use it, you can't find the method

You can write them together, or you can write methods in the whole tool class, and quote them here!
If we use spring, we can inject the interface as a bean (recommend)
plus parameters to open:
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)

/**
 * @author wangzhenjun
 * @date 2023/1/28 17:13
 */
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface ProductMapper {
    
    

    @Mapping(source = "length",target = "length",qualifiedByName = "cmToM")
    @Mapping(source = "width",target = "width",qualifiedByName = "cmToM")
    @Mapping(source = "high",target = "high",qualifiedByName = "cmToM")
    Product productVOToPrduct(ProductVO productVO);

    @Named("cmToM")
    default BigDecimal cmToM (BigDecimal oldValue){
    
    
        if (oldValue == null) {
    
    
            return BigDecimal.ZERO;
        }
        return oldValue.divide(new BigDecimal("100"));
    }
}

test:

@SpringBootTest
class DemoApplicationTests {
    
    

    @Autowired
    private ProductMapper productMapper;

    @Test
    void demoMapstruct(){
    
    

        ProductVO productVO = new ProductVO(1,"美丽家园地板",new BigDecimal(100),new BigDecimal(50),new BigDecimal(8));
        Product product = productMapper.productVOToProduct(productVO);

        System.out.println(product);
    }

}

Perfect conversion!

insert image description here

6. Common practice 4

In actual combat LocalDateTime、String相互转化的, you can go to the official website documents to find what you need later:

Add a field to the commodity class just now:

private String createdAt;

Also add one to VO:

private LocalDateTime createdAt;

Write a conversion class:
this is handed over to spring management, because ProductMapper is also handed over to spring management, if you don’t add it, you won’t find this class!

@Component
public class LocalDateTimeMapper {
    
    

    public String asString(LocalDateTime date) {
    
    
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return date != null ? date.format(formatter): null;
    }

    public LocalDateTime asLocalDateTime(String date) {
    
    
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return date != null ? LocalDateTime.parse(date,formatter) : null;
    }
}

ProductMapper modify:

uses = LocalDateTimeMapper.classJust use the class we wrote above!

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING,uses = LocalDateTimeMapper.class)
public interface ProductMapper {
    
    

    @Mapping(source = "length",target = "length",qualifiedByName = "cmToM")
    @Mapping(source = "width",target = "width",qualifiedByName = "cmToM")
    @Mapping(source = "high",target = "high",qualifiedByName = "cmToM")
    Product productVOToProduct(ProductVO productVO);

    @Named("cmToM")
    default BigDecimal cmToM (BigDecimal oldValue){
    
    
        if (oldValue == null) {
    
    
            return BigDecimal.ZERO;
        }
        return oldValue.divide(new BigDecimal("100"));
    }
}

have a test:

ProductVO productVO = new ProductVO(1,"美丽家园地板",
new BigDecimal(100),new BigDecimal(50),
new BigDecimal(8), LocalDateTime.now());
Product product = productMapper.productVOToProduct(productVO);
System.out.println(product);

Perfect conversion:

insert image description here

6. Summary

From introduction to actual combat, we are elegant programmers at this time!

For more examples, you can go to the official website to view:

mapstruct development documentation

Writing is not easy, please give me some support, your support is the driving force for my writing!

It's helpful to you, please don't be stingy with your little hands of getting rich and pay attention!

Pay attention to the editor's WeChat public account, and communicate and learn together! Read the article first!

Guess you like

Origin blog.csdn.net/qq_52423918/article/details/128778568