MapStruct Usage Summary

Keywords: Java

preface

Entity bean conversion is often inevitable. From the copyProperties of BeanUtil using traditional reflection technology to Object mapping, it basically stops at the dynamic compilation and generation of bytecode using annotation processor. Because it is a set/get calling code generated automatically during compilation, its performance during run-time is close to that of handwritten assignment (see the article for performance comparison: Java object attribute copying component -- object mapper Object mapping ). Secondly, the use of annotations to declare object transformation and copy is relatively simple, just like lombok, which avoids a lot of manual repeated code writing.

mapstruct, which has been used in the project for some time, is basically inseparable like Lombok. From one side, this reflects that there is room for evolution in the current definition and management of data entities in Java beans. For example, in the hierarchical design of processes, DO, DTO and Req/Res objects that isolate various entities in the DAO, Service and Controller layers need to be isolated, which leads to the problem of mutual conversion.

  • Official website: https://mapstruct.org/
  • Github address: https://github.com/mapstruct/mapstruct/

Basic Usage

Introduce dependency

Mapstruct requires JDK1.8, and introduces mapstruct's jar, which is only required in the source code and compilation time, and does not depend on the runtime;

It should be noted that the version of IntelliJ IDEA needs 2018.2.x +, because only the IDEA after this version supports annotation processors

Enable annotation processing in IntelliJ (Build, Execution, Deployment -> Compiler -> Annotation Processors)

Remember to enable annotation processors support in the IDEA

...
<properties>
    <mapstruct.version>1.4.2.Final</mapstruct.version>
</properties>
...
<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${mapstruct.version}</version>
        <optional>true</optional>
    </dependency>
</dependencies>
...
<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>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
...

If lombok is used at the same time, the configuration is as follows

...
<annotationProcessorPaths>
   <path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
  </path>
  <path>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>${mapstruct.version}</version>
  </path>
</annotationProcessorPaths>
...

An example of an entity Bean is as follows:

@Data
public class User {
    private long id;
    private String name;
    private List<Address> addresses;
}
      
@Data
public class AddressDto {
    private String cityCode;
    private String cityName;
}

@Data
public class UserDto {
    private long id;
    private String name;
    private List<AddressDto> addresses;
}

@Data
public class AddressDto {
    private String cityCode;
    private String cityName;
}

Define the corresponding mapped interface

import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

@Mapper
public interface UserCopier {
    UserCopier INSTANCE = Mappers.getMapper(UserCopier.class);

    @Mappings({})
    UserDto userToDtoUser(User user);

    List<UserDto> userToDtoUser(List<User> list);

}

Mapping interface usage

User user = new User();
user.setId(1);
Address address = new Address();
address.setCityCode("10");
address.setCityName("beijing");
user.setAddresses(Arrays.asList(address));
UserDto userDto = UserCopier.INSTANCE.userToDtoUser(user);

// Copy List
List<User> users ...
List<UserDto> list =  UserCopier.INSTANCE.userToDtoUser(users);

You can see that the UserCopierImpl class is automatically generated after compilation

public class UserCopierImpl implements UserCopier {
    public UserCopierImpl() {
    }

    public UserDto userToDtoUser(User user) {
        if (user == null) {
            return null;
        } else {
            UserDto userDto = new UserDto();
            userDto.setId(user.getId());
            userDto.setName(user.getName());
            userDto.setAddresses(this.addressListToAddressDtoList(user.getAddresses()));
            return userDto;
        }
    }

    public List<UserDto> userToDtoUser(List<User> list) {
        if (list == null) {
            return null;
        } else {
            List<UserDto> list1 = new ArrayList(list.size());
            Iterator var3 = list.iterator();

            while(var3.hasNext()) {
                User user = (User)var3.next();
                list1.add(this.userToDtoUser(user));
            }

            return list1;
        }
    }

    protected AddressDto addressToAddressDto(Address address) {
        if (address == null) {
            return null;
        } else {
            AddressDto addressDto = new AddressDto();
            addressDto.setCityCode(address.getCityCode());
            addressDto.setCityName(address.getCityName());
            return addressDto;
        }
    }

    protected List<AddressDto> addressListToAddressDtoList(List<Address> list) {
        if (list == null) {
            return null;
        } else {
            List<AddressDto> list1 = new ArrayList(list.size());
            Iterator var3 = list.iterator();

            while(var3.hasNext()) {
                Address address = (Address)var3.next();
                list1.add(this.addressToAddressDto(address));
            }

            return list1;
        }
    }
}

Use of other common features

Attribute mapping

You can specify the source and target fields, which are used when the attribute names of the source object and the target object are different;

@Mappings({
	@Mapping(source = "userName", target = "loginName")
})
UserDto userToDtoUser(UserReq user);

You can specify attribute mappings for nested objects

Assign the addressCityName of the source object to the cityName attribute of the address member object of the target object, and assign all attributes of the account member object of the source object to the same attribute under the target object.

@Mappings({
	@Mapping(source = "addressCityName", target = "address.cityName")
    @Mapping(source = "account", target = ".")
})
UserDto userToDtoUser(UserReq user);

Type conversion

MapStruct handles type conversions automatically in many cases. For example, if a property is of type int in the source bean but String in the target bean, the generated code will transparently perform the conversion by calling String#valueOf (int) and Integer#parseInt (String) respectively.

Between all Java basic data types and their corresponding wrapper types, such as int and Integer, boolean and boolean, etc. The generated code is null aware, that is, null checking is performed when the wrapper type is converted to the corresponding base type.

Numeric and date formatting

@Mapper
public interface CarMapper {

    @Mapping(source = "price", numberFormat = "$#.00")
    CarDto carToCarDto(Car car);

    @IterableMapping(numberFormat = "$#.00")
    List<String> prices(List<Integer> prices);
    
    @Mapping(source = "manufacturingDate", dateFormat = "dd.MM.yyyy")
    CarDto carToCarDto(Car car);

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

expression

When mapping, Java code can be called through expression, which is automatically generated in the end

@Mapper
public interface SourceTargetMapper {

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

    @Mapping(target = "timeAndFormat",
         expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )")
    Target sourceToTarget(Source s);
}

For more detailed usage, please refer to the usage document on the official website.

Posted by wolfrock on Sun, 21 Nov 2021 21:22:48 -0800