preface

Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”. The back-end application is always exchanging objects with each other, and each object is still new. ! Ha ha ha ~ don’t get me wrong, I mean Object. When it comes to objects, we have to talk about the mutual conversion between objects, then we need a special tool to solve the conversion problem, after all, every field get/set will be very troublesome, trouble is second, it affects the elegance of the code, let a person feel very low; May someone will say, why not use org. Springframework. Beans. BeanUtils shallow copy to complete the transformation between object and object? I can only say that BeanUtils is not flexible enough. If the attribute name is different, it needs to be assigned manually. I feel that there is no MapStruct smell

instructions

What are VO, DTO, DO, and PO?

A:

  1. VO(View Object) : A View Object, used in the View page layer, that encapsulates the data in a defined page or component into an Object
  2. DTO(Data Transfer Object) : Data Transfer Object, this concept comes fromJ2EEThe original purpose is to provide coarse-grained data entities for the distributed application of EJB, to reduce the number of distributed calls, thereby improving the performance of distributed calls and reducing network load, but here, used to show the data transfer object between the layer and the service layer
  3. DO(Domain Object) : Domain objects are tangible or intangible business entity classes abstracted from the real world
  4. PO(Persistent Object) : Persistent Object, which forms a one-to-one mapping relationship with the data structure of the persistence layer (usually relational database)

MapStruct

What

Officially, MapStruct is a code generator that follows the principle of convention over configuration, greatly simplifying the implementation of mappings between Java bean types. You only need to define a Mapper interface, MapStruct will automatically implement this mapping interface, avoiding complex mapping implementation.

Why

  • The generated mapping code uses simple method calls and is therefore fast, type-safe, and easy to understand

  • MapStruct aims to simplify the tedious and error-prone task of converting objects to and from object models by being as automated as possible

  • Compared to other mapping frameworks, MapStruct generates bean maps at compile time to ensure high performance, allowing for fast developer feedback and thorough error checking

How

Step 1: LeadMapStructRely on

    <properties>
        <org.mapstruct.version>1.4.2. The Final</org.mapstruct.version>
    </properties>
    
    <! --MapStruct-->
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
    
    <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>
                    <encoding>UTF-8</encoding>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
         </plugins>
    </build>
Copy the code

Write entity class

This class is an entity class automatically generated by MP based on the TB_FRUIT table structure, involving eight fields

/** * <p> ** Fruit entity * </p> **@author HUALEI
 * @sinceThe 2021-09-04 * /

@TableName("tb_fruit")
@Data
public class Fruit {

      /**
     * 主键id
     */
      @TableId(value = "fruit_id", type = IdType.AUTO)
      private Long fruitId;

      /** * fruit name */
      private String fruitName;

      /** * fruit sales */
      private Integer fruitSale;

      /** * Local icon address */
      private String localIcon;

      /** * Alternate network icon address */
      private String spareIcon;

      /** * logical delete 0 not deleted, 1 deleted */
      @TableLogic
      private Byte isDeleted;

      /** * Changes the update time */
      @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
      @TableField(fill = FieldFill.INSERT_UPDATE)
      private Date updateTime;

      /** * Optimistic lock identifier, used to control unique modification operation */
      @Version
      private Integer version;
}
Copy the code

Write conversion object

Depending on the entity class, specify which field attributes the converted object should contain

Here I take two VO objects for example. In one of them, six properties are randomly selected from Fruit and the property names remain the same. But in the other, there are three fields in total, FruitVO2, which are different from the property names in the entity class

@Data
public class FruitVO implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long fruitId;
    private String fruitName;
    private Integer fruitSale;
    private String localIcon;
    private String spareIcon;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date updateTime;
}

@Data
public class FruitVO2 implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;
    private String name;
    private Integer sale;
}
Copy the code

Write mapping interface

After the object to be transformed is identified, the mapping interface is written to form a one-to-one mapping between source object attributes and target object attributes

Note:@MapperAnnotations areorg.mapstruct.factory.MappersBelow, must not introduce wrong cough up!!

@Mapper
public interface FruitMap {

    FruitMap mapper = Mappers.getMapper(FruitMap.class);

    @Mappings({ @Mapping(source = "fruitId", target = "fruitId"), @Mapping(source = "fruitName", target = "fruitName"), @Mapping(source = "fruitSale", target = "fruitSale"), @Mapping(source = "localIcon", target = "localIcon"), @Mapping(source = "spareIcon", target = "spareIcon"), @Mapping(source = "updateTime", target = "updateTime") })
    FruitVO pojo2vo(Fruit fruit);
    Fruit vo2pojo(FruitVO fruitVO);

    @Mappings({ @Mapping(source = "fruitId", target = "id"), @Mapping(source = "fruitName", target = "name"), @Mapping(source = "fruitSale", target = "sale") })
    FruitVO2 pojo2vo2(Fruit fruit);

    List<FruitVO> pojoList2vo(List<Fruit> fruits);

    List<FruitVO2> pojoList2vo2(List<Fruit> fruits);

    @Mappings({ @Mapping(source = "updateTime", target = "orderId", qualifiedByName = "setOrderIdByUpdateTime"), @Mapping(source = "fruitId", target = "shortage.id"), @Mapping(source = "fruitSale", target = "shortage.lackNum") })
    PurchaseList pojo2PurchaseList(Fruit fruit);

    @Named("setOrderIdByUpdateTime")
    default Long setOrderIdByUpdateTime(Date updateTime) {
        returnSystem.currentTimeMillis() - updateTime.getTime(); }}Copy the code

This code begins by creating an instance of the FruitMap type, Mapper, which is the entry point for our next call to the methods in this interface

  1. First, look at the first interface method,pojo2voIs to convert an entity-class object toVOObject, because of the same attribute name between them, is not used@MappingsOne by one corresponds to the properties in the target object, but it is ok to write…vo2pojoIs toVOconvertPOI’ve already declared mappings between their properties, soMapStructThe underlying implementation automatically converts based on the declared rules
  2. Second, thepojo2vo2The method uses@MappingsThe annotations specify the mapping that allows the mapping to be done in the case of different attribute names
  3. In the end,pojoList2vopojoList2vo2Is toPOSet toVOCollection, since the object layer can be converted to each other, the transformation between collections is just an outer layerforCycle”

Call interface | test the transformation

Automatically inject the object conversion interface, call the corresponding method in the interface to complete the conversion

@SpringBootTest
class MapStructToolTests {

    @Autowired
    private FruitMapper fruitMapper;

    @Test
    // Use MapStruct tool to convert entity class into VO object, VO field and POJO field are the same case
    void pojo_to_vo_field_same(a) {
        Fruit fruit = fruitMapper.selectById(18L);
        FruitVO fruitVO = FruitMap.mapper.pojo2vo(fruit);
        System.out.println(fruitVO);
    }

    @Test
    // Use the MapStruct tool to convert the entity class into VO objects
    void pojo_to_vo2_field_different(a) {
        Fruit fruit = fruitMapper.selectById(15L);
        // Use @mappings ({@mapping (...)); . }) annotations
        FruitVO2 fruitVO2 = FruitMap.mapper.pojo2vo2(fruit);
        System.out.println(fruitVO2);
    }

    @Test
    // Not only can we convert the entity class to VO, but also the other way around
    void vo2_to_pojo_field_same(a) {
        Fruit fruit = fruitMapper.selectById(1L);
        System.out.println(fruit);
        FruitVO fruitVO = FruitMap.mapper.pojo2vo(fruitMapper.selectById(2L));
        System.out.println(fruitVO);
        Fruit fruit1 = FruitMap.mapper.vo2pojo(fruitVO);
        System.out.println(fruit1);
    }
    
    @Test
    // Collection conversions based on object conversions (same fields)
    void pojo_list_to_vo_field_same(a) {
        QueryWrapper<Fruit> wrapper = new QueryWrapper<>();
        wrapper.le("fruit_id".3);
        List<Fruit> fruits = fruitMapper.selectList(wrapper);
        List<FruitVO> fruitVOs = FruitMap.mapper.pojoList2vo(fruits);
        fruitVOs.forEach(System.out::println);
    }

    @Test
    // Collection conversions based on object conversions (fields different)
    void pojo_list_to_vo2_field_different(a) {
        QueryWrapper<Fruit> wrapper = new QueryWrapper<>();
        wrapper.le("fruit_id".3);
        List<Fruit> fruits = fruitMapper.selectList(wrapper);
        // The mapping between pojo -> vo is written only once
        List<FruitVO2> fruitVO2s = FruitMap.mapper.pojoList2vo2(fruits);
        for(FruitVO2 fruitVO2 : fruitVO2s) { System.out.println(fruitVO2); }}}Copy the code

All passed the test, bingo ~ friends, you can try oh, use all say sweet……

Other USES

Attribute mapping object

Write two classes o( ̄)  ̄)o at random

// @data Public class PurchaseList {// Order number private Long orderId; // The amount of fruit in short supply }Copy the code
// @data public class Shortage {// void fruit id private Long id; // void fruit id private Long ID; // void fruit id private Long ID; Private Integer lackNum; }Copy the code

What does MapStruct do with the complexity of mapping from Fruit to PurchaseList, which contains two properties, one of which is an object?

Add the following methods to the FruitMap:

Note: write method implementations in the interface to usedefaultThe keyword

@Mappings({ @Mapping(source = "updateTime", target = "orderId", qualifiedByName = "setOrderIdByUpdateTime"), @Mapping(source = "fruitId", target = "shortage.id"), @Mapping(source = "fruitSale", target = "shortage.lackNum") })
PurchaseList pojo2PurchaseList(Fruit fruit);
Copy the code

The mapping relationship is as follows: Fruit ID is mapped to the id of the missing fruit; Fruit sales are mapped to the number of vacancies

What is the use of the qualifiedByName attribute? If you have this question, please go to ☟

Conversion intermediate value processing

What if I specify that the value of the order number attribute in PurchaseList is the time difference by current time minus data update time? Can I use an intermediate function to do the assignment before the conversion? The answer is yes, as follows:

@Named("setOrderIdByUpdateTime")
default Long setOrderIdByUpdateTime(Date updateTime) {
    return System.currentTimeMillis() - updateTime.getTime();
}
Copy the code

At this point, use the @named annotation to name the intermediate handler setOrderIdByUpdateTime(), Would it be easy to use qualifiedByName to find the updataTime property in the source object by the name specified in the intermediate handler and take that property as an argument, returning the result of the conversion?

The underlying implementation

Unknown digg friend: I think kangkang MapStruct bottom in the end do what?

Through the decompilation function of YYDS IDE to view the compiled implementation class, as follows:

@Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2021-09-09T13:27:31+0800", comments = "version: Final, Compiler: JavAC, Environment: Java 1.8.0_221 (Oracle Corporation)")
public class FruitMapImpl implements FruitMap {

    @Override
    public FruitVO pojo2vo(Fruit fruit) {
        if ( fruit == null ) {
            return null;
        }

        FruitVO fruitVO = new FruitVO();

        fruitVO.setFruitId( fruit.getFruitId() );
        fruitVO.setFruitName( fruit.getFruitName() );
        fruitVO.setFruitSale( fruit.getFruitSale() );
        fruitVO.setLocalIcon( fruit.getLocalIcon() );
        fruitVO.setSpareIcon( fruit.getSpareIcon() );
        fruitVO.setUpdateTime( fruit.getUpdateTime() );

        return fruitVO;
    }

    @Override
    public Fruit vo2pojo(FruitVO fruitVO) {
        if ( fruitVO == null ) {
            return null;
        }

        Fruit fruit = new Fruit();

        fruit.setFruitId( fruitVO.getFruitId() );
        fruit.setFruitName( fruitVO.getFruitName() );
        fruit.setFruitSale( fruitVO.getFruitSale() );
        fruit.setLocalIcon( fruitVO.getLocalIcon() );
        fruit.setSpareIcon( fruitVO.getSpareIcon() );
        fruit.setUpdateTime( fruitVO.getUpdateTime() );

        return fruit;
    }

    @Override
    public FruitVO2 pojo2vo2(Fruit fruit) {
        if ( fruit == null ) {
            return null;
        }

        FruitVO2 fruitVO2 = new FruitVO2();

        fruitVO2.setId( fruit.getFruitId() );
        fruitVO2.setName( fruit.getFruitName() );
        fruitVO2.setSale( fruit.getFruitSale() );

        return fruitVO2;
    }

    @Override
    public List<FruitVO> pojoList2vo(List<Fruit> fruits) {
        if ( fruits == null ) {
            return null;
        }

        List<FruitVO> list = new ArrayList<FruitVO>( fruits.size() );
        for ( Fruit fruit : fruits ) {
            list.add( pojo2vo( fruit ) );
        }

        return list;
    }

    @Override
    public List<FruitVO2> pojoList2vo2(List<Fruit> fruits) {
        if ( fruits == null ) {
            return null;
        }

        List<FruitVO2> list = new ArrayList<FruitVO2>( fruits.size() );
        for ( Fruit fruit : fruits ) {
            list.add( pojo2vo2( fruit ) );
        }

        return list;
    }

    @Override
    public PurchaseList pojo2PurchaseList(Fruit fruit) {
        if ( fruit == null ) {
            return null;
        }

        PurchaseList purchaseList = new PurchaseList();

        purchaseList.setShortage( fruitToShortage( fruit ) );
        purchaseList.setOrderId( setOrderIdByUpdateTime( fruit.getUpdateTime() ) );

        return purchaseList;
    }

    protected Shortage fruitToShortage(Fruit fruit) {
        if ( fruit == null ) {
            return null;
        }

        Shortage shortage = new Shortage();

        shortage.setId( fruit.getFruitId() );
        shortage.setLackNum( fruit.getFruitSale() );

        returnshortage; }}Copy the code

Get /set = get/set = get/set Indeed, MapStruct is essentially a code generator that saves us a lot of tedious, repetitive work. It’s fast and easy to understand, easy to code, and more flexible than BeanUtils

In summary, do you choose BeanUtils? Or do you want to learn MapStruct? Hope to see here you have some help and inspiration (^▽^)

More and more

For more details on how to use it, see Doraemon’s Portal on MapStruct’s website

At the end

Writing is not easy, welcome everyone to like, comment, your attention, like is my unremitting power, thank you to see here! Peace and Love.