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 ()