MapStruct最详细的使用教程,别在用BeanUtils.copyProperties ()

Posted 徐刘根

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MapStruct最详细的使用教程,别在用BeanUtils.copyProperties ()相关的知识,希望对你有一定的参考价值。

本文转载自:https://blog.csdn.net/qq_40194399/article/details/110162124

1.为什么使用MapStruct

在开发中你可曾遇到如下这样的问题?MyBtatis从数据库中查询的数据映射到domain的实体类上,然后有时候需要将domain的实体类映射给前端的VO类,用于展示。

如下所示,假如Student是domain,而给前端展示的为StudentVO。

有没有什么优雅的解决方式呢?可能你的第一反应就是使用Spring的BeanUtils.copyProperties (),但是BeanUtils.copyProperties ()只能转换类中字段名字一样且类型一样的字段。

由于BeanUtils.copyProperties ()采用的是反射,实际上当重复调用时效率是比较低的。(实际测试实际测试Spring的BeanUtils在生成 次数为1000000时需要1.6秒,而使用MapStruct仅需要69毫秒)。

2.MapStruct的依赖

首先导入Maven依赖

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.3.1.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>1.3.1.Final</version>
</dependency>

插件:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.5.1</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <annotationProcessorPaths>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.3.1.Final</version>
            </path>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.16.22</version>
            </path>
        </annotationProcessorPaths>
        <compilerArgs>
            <arg>-Amapstruct.defaultComponentModel=spring</arg>
        </compilerArgs>
    </configuration>
</plugin>

3.当成员变量名相同时的使用

首先domain的Studnet类和StudentVO类如下,可以看到字段是完全一致的。其中@Data注解是lombok的表示含义如下,而@AllArgsConstructor则是提供所有参数的有参构造。



写一个Mapper接口StudentMapper,此处的Mapper注解不是MyBtais的Mapper注解。

接下来测试一下,看一下生成的结果。


4.成员变量名不相同时的使用

Studnet类的age和name与StudentVO类的ageVO和nameVO对应不上时


在Mapper类中加入@Mapping的注解指定原对象的字段名和要被对应上的字段名。其中@Mappings表示多个字段需要对应,如果只是一个可以使用@Mapping


接下来测试一下,看一下生成的结果。


5.多参数源映射

某些时候,我们的源不是一个,例如从数据库中查询出来了学生和老师,我们需要将老师的名字给VO的name字段,学生的年龄给VO的age字段时可以使用多参数源的映射方式。


在Mapper类的toStudentVO可以看到带了两个参数,然后在@Mapping中使用形参的名字去点字段的名。

接下来测试一下,看一下生成的结果。


6.多层嵌套映射

有些时候我们需要多层映射,例如老师类中有自己的一个老婆类(男老师),然后我们需要将老师类中的老婆类的名字,赋值给VO,而年龄则使用学生的年龄。听上去怪怪的,就像学生有了老师的老婆😂😂。



同样可以在Mapper类中使用符号"."的方式进行映射。

接下来测试一下,看一下生成的结果。


7.更新现有的Bean

某些情况下,你需要不创建目标类型的新实例,而是更新该类型的现有实例的映射。可以通过为目标对象添加参数并使用@MappingTarget标记此参数来实现此类映射。

例如Student我们将学生类的名字和年龄映射到VO中,但是不创建新的实例。

在Mapper接口中使用@MappingTarget注解,被@MappingTarget注解标记的实例将从未被标记中进行的实例中进行映射。

接下来测试一下,看一下生成的结果。


8.映射器工厂

前面我们在Mapper接口中代码中一直有一行代码,如下所示,是MapStruct为我们提供的映射工厂,指定接口类型后自动帮我们创建接口的实现,且保证是线程安全的单例,无需自己手动创建。

9.依赖注入

某些时候尤其是在做项目时,我们用到了Sping,希望映射后的新实例是交给Spring管理。这时候就需要进行依赖注入了。只需要在Mapper接口中的@Mapper注解中加入componentModel = "spring"即可。

10.数据类型转换

映射属性在源对象和目标对象中具有相同的类型,这种情况不全有。例如,属性在源bean中可以是int类型,但在目标bean中可以是Long类型。另一个例子是对其他对象的引用,这些对象应该映射到目标模型中的相应类型。例如:Teachr类可能有一个Wife类型的属性wife,在映射VO对象时需要将其转换为StudentVO对象。

在许多情况下,MapStruct会自动处理类型转换。例如,如果属性在源bean中的类型为int,但在目标bean中的类型为String,则生成的代码将分别通过调用String.valueOf(int)和Integer.parseInt(String)来透明地执行转换。

通过案例来实现从int转换为String 从BigDecimal到String的转换 以及从Date到String的转换

输出结果如下所示


11.映射集合

在映射集合的时候,我们同样可以进行类型之间的转换,如下所示使用@MapMapping注解指定输出类型即可。

输出结果如下所示


当然MapStruct也支持其他各种类型的集合映射,上面只是举例了Map的映射

12.映射枚举

MapStruct支持生成将一个Java枚举类型映射到另一个Java枚举类型的方法。默认情况下,源枚举中的每个常量都映射到目标枚举类型中具有相同名称的常量。如果需要,可以使用@ValueMapping注解将源枚举中的常量映射到具有其他名称的常量。源枚举中的几个常量可以映射到目标类型中的相同常量。

Student中是SexEnum枚举,而StudentVO中是Sex2Enum,且枚举中的值是一致时,我们需要将Student中的映射到StudentVO中,此时只需要使用@Mapping来指定映射源和目标源的名称即可




当枚举值一样时,直接使用@Mapping来指定映射源和目标源的名称即可

当枚举值不一致时,使用@ValueMapping注解。




使用@ValueMapping注解,同时由于Student和StudentVO中的枚举类型不一致,所以之前的@Mapping注解也要使用。

13.对象工厂

有时候由于目标实例的构造方法被私有化后,我们使用原来的方式没办法进行,原因是MapStruct会在编译时去帮你实现,其中包含了调用构造方法。所以我们可以定义工厂的形式来生成实例,而让MapStruct去调用工厂来生成实例,而不再使用构造方法。

有我们私有化了StudentVO的构造方法,如果直接使用MapStruct进行映射是会报错的。

指定工厂,同时在Mapper接口中的@Mapper注解上加入工厂的class


输出如下


14.自定义映射

在某些情况下,可能需要定制生成的映射方法,在目标对象中设置一个无法由MapStruct生成的方法实现时,可以使用自定义映射来完成。假如我们的StudentVO中的age是无法生成的。

首先定义类,然后实现Mapper接口,在重写的方法中写上需要的逻辑,且在Mapper接口中加入@DecorateWith注解,指定自定义映射的class。


测试输出结果,可以看到先给age值为0,最后输出为100.

上面的MapStruct只写了一些常用的,以及我觉得可能会用到的,其中MapStruct还包含很多种用法,如果你想完全的了解他的所有功能,可以参考MapStruct的官方文档,文档地址可以在最下面可以看到。

文档地址:http://mapstruct.org/documentation/stable/reference/html/


如下为更新内容:

15.使用expression表达式

15.1 测试案例1

@Data
public class ChanceDO implements Serializable {

    private String station;

    private String attr;

    private String bizType;
}
@Data
public class ChanceInfoDTO implements Serializable {

    private Boolean isStation;

    private String bizType;

    private Map<String, String> attr;
}

@Mapper
public interface ChanceConverter {

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

    @Mappings({
        @Mapping(target = "attr",
            expression = "java( com.alibaba.fastjson.JSON.parseObject(chanceDO.getAttr(),new "
                + "com.alibaba.fastjson.TypeReference<java.util.Map<String, String>>() {}))"),
        @Mapping(target = "isStation",
            expression = "java( org.apache.commons.lang3.BooleanUtils.toBoolean(chanceDO.getStation()))"),
        @Mapping(target = "bizType", constant = "COMMON_BIZ_TYPE")
    })
    ChanceInfoDTO covert2ChanceInfoDTO(ChanceDO chanceDO);
}

测试代码&执行结果:


生成的代码:

15.2 测试案例1

ChanceInfoDTO和ChanceDO和上述保持一致

@Mappings({
        @Mapping(target = "attr",
            expression = "java( java.util.Optional.ofNullable( chanceInfo.getAttr() ).map( com.alibaba.fastjson.JSON "
                + ":: toJSONString).orElse(null))"),
        @Mapping(target = "station", source = "isStation")
    })
    ChanceDO covert2ChanceDO(ChanceInfoDTO chanceInfo);

测试代码和结果:

生成的代码:

以上是关于MapStruct最详细的使用教程,别在用BeanUtils.copyProperties ()的主要内容,如果未能解决你的问题,请参考以下文章

MapStruct最详细的使用教程,别在用BeanUtils.copyProperties ()

MapStruct最详细的使用教程,别在用BeanUtils.copyProperties ()

MapStruct最详细的使用教程,别在用BeanUtils.copyProperties ()

优雅的转换Bean对象 mapstruct使用笔记详解

Bean 字段复制利器 MapStruct

Bean映射工具MapStruct介绍