高性能实体映射工具-Mapstruct

813 阅读4分钟

一、简介

Mapstruct是一种实体类映射框架,能够通过Java注解将一个实体类的属性安全地赋值给另一个实体类。有了Mapstruct,只需要定义一个映射器接口,声明需要映射的方法,在编译过程中,Mapstruct会自动生成该接口的实现类,实现将源对象映射到目标对象的效果。

二、优点

1、安全性高,因为是编译期就实现源对象到目标对象的映射, 如果编译器能够通过,运行期就不会报错。

2、性能高,通过使⽤普通⽅法(getter/setter)调⽤⽽不是反射来快速执⾏。

3、如果有如下问题,编译时会抛出异常

  • 映射不完整(并非所有目标属性都被映射)
  • 映射不正确(找不到正确的映射方法或类型转换)

4、代码独立,没有运行时的依赖,更加规范

三、实践

<mapstruct.version>1.5.2.Final</mapstruct.version>
<!--包含mapstruct所需的注解-->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>${mapstruct.version}</version>
</dependency>

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <source>1.8</source> <!-- depending on your project -->
        <target>1.8</target> <!-- depending on your project -->
        <annotationProcessorPaths>
        	<!--包含⽣成映射器实现的注释处理器-->
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>${mapstruct.version}</version>
            </path>
            <!-- other annotation processors -->
        </annotationProcessorPaths>
    </configuration>
</plugin>

3.1、基本映射

要创建映射器,只需使⽤所需的映射⽅法定义Java接口,并使⽤@Mapper 注解对其进⾏注释: @Mapper注解的componentModel属性有五种赋值:

  • MappingConstants.ComponentModel.DEFAULT:默认,不使用任何组建类型,可以通过Mappers.getMapper(Class) 方式获取实例对象
  • MappingConstants.ComponentModel.SPRING:在实现类上注解 @Component,可通过 @Autowired 方式注入
  • MappingConstants.ComponentModel.JSR330:实现类上添加@javax.inject.Named 和@Singleton注解,可以通过 @Inject注解获取。
  • MappingConstants.ComponentModel.CDI:不懂
  • MappingConstants.ComponentModel.JAKARTA:不懂
public class UserDTO {
    private Long id;
    private String username;
    private String password;
    private int age;
    private Boolean bald;
    private List<String> roles;
}

public class UserVO {
    private Long id;
    private String username;
    private String password;
    private Integer age;
    private Boolean bald;
    private List<String> roles;
}

@Mapper
public interface UserMap {
	//循环调用的场景下提高性能的关键,通过UserMap.INSTANCE获取实例
	UserMap INSTANCE = Mappers.getMapper(UserMap.class);

    UserVO userDTOToVO(UserDTO userDTO);
}

3.2 指定方式映射

使用@Mapping注解可以指定映射目标字段的源字段、日期格式化、数字格式化、默认值或常量、表达式、忽略字段、空字段检查和映射策略、指定映射方法(不使用getter/setter)等,满足特定的映射需求。@Mapping注解的target属性必填。

//指定源字段,目标对象和源对象的属性名不一致
//指定源字段
@Mapping(target = "pwd", source = "password")
UserVO userDTOToVO(UserDTO userDTO);

当需要对多个字段进行操作时,可以使用@Mappings,也可以在方法上叠加@Mapping注解

@Mappings({
        //指定源字段
        @Mapping(target = "pwd", source = "password"),
        //日期格式化,如果属性从字符串映射到日期,则该格式字符串可由SimpleDateFormat处理,反之亦然,当映射枚举常量时,将忽略所有其他属性类型
        @Mapping(target = "createTime", dateFormat = "yyyy-MM-dd"),
        //数字格式化
        @Mapping(target = "age", numberFormat = "$#.0"),
        //指定默认值或常量,两者的区别是常量无论源对象是否有值,都会被覆盖
        @Mapping(target = "bald", defaultValue = "true"),
        //表达式,这个属性不能与source、defaultValue、defaultExpression、qualifiedBy、qualifiedByName或constant一起使用。
        //需要包含完整的包名,否则可能找不到类
        @Mapping(target = "username", expression = "java(org.apache.commons.lang3.StringUtils.substring(userDTO.getUsername(),0,2))"),
        //忽略字段
        @Mapping(target = "id", ignore = true)
})
UserVO userDTOToVO(UserDTO userDTO);

指定映射时调用的方法

//方式一:
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface ToLocalDateTime {
}

public class LocalDateUtil{
    /**
     * 将Date类型的时间转换为LocalDateTime类型
     * @param date
     * @return
     */
    @ToLocalDateTime
    public static LocalDateTime convert(Date date){
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    }
}

//需要用uses属性导入方法所在的类
@Mapper(uses = LocalDateUtil.class)
public interface UserMap {

    @Mapping(target = "createTime", qualifiedBy = ToLocalDateTime.class)
    UserVO userDTOToVO(UserDTO userDTO);
}

--------------------------------------------------------------------------------------------------------
//方式二:
public class LocalDateUtil{
    /**
     * 将Date类型的时间转换为LocalDateTime类型
     * @param date
     * @return
     */
    @Named("convertToLocalDateTime")
    public static LocalDateTime convert(Date date){
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    }
}

@Mapper(uses = LocalDateUtil.class)
public interface UserMap {

    @Mapping(target = "createTime", qualifiedByName = "convertToLocalDateTime")
    UserVO userDTOToVO(UserDTO userDTO);
}

3.3更新bean对象

并不想要返回一个新的对象,而是想要更新传入对象的一些属性值时,将对象缺失的信息进行补充,这时可以使用@MappingTarget注解注释

/**
  * 传入两个对象,userDTO会被userVO的值覆盖
  *
  * @param userVO
  * @param userDTO
  */
 void updateUserDTO(UserVO userVO, @MappingTarget UserDTO userDTO);

3.4多对一映射

我们在实际的业务中少不了将多个对象转换成一个的场景。MapStruct 当然也支持多转一的操作。

/**
 * 源对象属性指定具体的字段
 * @param userDTO
 * @param processDef
 * @return
 */
@Mapping(target = "id",source = "processDef.id")
UserVO convertUser(UserDTO userDTO, ProcessDef processDef);

3.5逆映射

在双向映射的情况下,例如从实体到DTO以及从DTO到实体,前向方法和反向方法的映射规则通常是相似的,并且可以通过切换source和来简单地反转target。使用注释@InheritInverseConfiguration表示方法应继承相应反向方法的反向配置

@Mapper(uses = LocalDateUtil.class)
public interface UserMap {

    @Mapping(target = "createTime", qualifiedByName = "convertToLocalDateTime")
    UserVO userDTOToVO(UserDTO userDTO);

    @InheritConfiguration
    UserDTO userVOToDTO(UserVO userVO);
    
}

3.6map映射

当需要将一个map对象映射到另一个map对象时可以使用@MapMapping注解

public interface SourceTargetMapper {

    @MapMapping(valueDateFormat = "dd.MM.yyyy")
    Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}

@MapMapping注解针对key和value提供了很多的属性,可以参照@Mapping注解,会按照配置分别对key和value进行映射。

package org.mapstruct;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.mapstruct.control.MappingControl;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface MapMapping {
    String keyDateFormat() default "";

    String valueDateFormat() default "";

    String keyNumberFormat() default "";

    String valueNumberFormat() default "";

    Class<? extends Annotation>[] keyQualifiedBy() default {};

    String[] keyQualifiedByName() default {};

    Class<? extends Annotation>[] valueQualifiedBy() default {};

    String[] valueQualifiedByName() default {};

    Class<?> keyTargetType() default void.class;

    Class<?> valueTargetType() default void.class;

    NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;

    Class<? extends Annotation> keyMappingControl() default MappingControl.class;

    Class<? extends Annotation> valueMappingControl() default MappingControl.class;
}

以上基本能满足日常开发的需要,如有更多的需求或学习兴趣,可参阅官方文档:mapstruct.org/documentati…