Mapstruct 使用教程

╰半橙微兮° 2024-02-20 12:06 123阅读 0赞

Mapstruct 版本1.5.0.Beta1
官方文档
案例-github

前言

MapStruct是一个Java注释处理器,用于生成类型安全的bean映射类。

您要做的就是定义一个映射器接口,该接口声明任何必需的映射方法。在编译期间,MapStruct将生成此接口的实现。此实现使用简单的Java方法调用在源对象和目标对象之间进行映射,即没有反射或类似内容。

与手动编写映射代码相比,MapStruct通过生成繁琐且易于出错的代码来节省时间。遵循配置方法上的约定,MapStruct使用合理的默认值,但在配置或实现特殊行为时不加理会。
与动态映射框架相比,MapStruct具有以下优点:

  1. 通过使用普通方法调用(settter/getter)而不是反射来快速执行
  2. 编译时类型安全性:只能映射相互映射的对象和属性,不能将order实体意外映射到customer DTO等。
  3. 如果有如下问题,编译时会抛出异常
    3.1 映射不完整(并非所有目标属性都被映射)
    3.2 映射不正确(找不到正确的映射方法或类型转换)
  4. 可以通过freemarker定制化开发

1. 设置

MapStruct是基于JSR 269的Java注释处理器,因此可以在命令行构建(javac,Ant,Maven等)以及您的IDE中使用。

它包含以下工件:

  1. org.mapstruct:mapstruct:包含必需的注释,例如@Mapping
  2. org.mapstruct:mapstruct-processor:包含注释处理器,该注释处理器生成映射器实现

1.1 Maven

对于基于Maven的项目,将以下内容添加到您的POM文件中以使用MapStruct:

  1. <!--mapStruct依赖 高性能对象映射-->
  2. <!--mapstruct核心-->
  3. <dependency>
  4. <groupId>org.mapstruct</groupId>
  5. <artifactId>mapstruct</artifactId>
  6. <version>1.5.0.Beta1</version>
  7. </dependency>
  8. <!--mapstruct编译-->
  9. <dependency>
  10. <groupId>org.mapstruct</groupId>
  11. <artifactId>mapstruct-processor</artifactId>
  12. <version>1.5.0.Beta1</version>
  13. </dependency>

Lombok依赖:(版本最好在1.16.16以上,否则会出现问题)通常是和lombok一起使用

  1. <dependency>
  2. <groupid>org.projectlombok</groupid>
  3. <artifactid>lombok</artifactid>
  4. <version>${lombok.version}</version>
  5. // 版本号 1.18.12
  6. </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

下载插件(不是必须的,但是挺好用)
idea中下载 mapstruct support 插件,安装重启Idea:
在这里插入图片描述
在参数上,按 ctrl + 鼠标左键 ,能够自动进入参数所在类文件
在这里插入图片描述

2. 定义一个映射器

2.1 基本映射

要创建映射器,只需使用所需的映射方法定义一个Java接口,并用注释对其进行org.mapstruct.Mapper注释:
该@Mapper注释将使得MapStruct代码生成器创建的执行PersonMapper 过程中生成时的界面。

在生成的方法实现中,源类型(例如Person)的所有可读属性都将被复制到目标类型(例如PersonDTO)的相应属性中:

  1. 当一个属性与其目标实体对应的名称相同时,它将被隐式映射。
  2. 当属性在目标实体中具有不同的名称时,可以通过@Mapping注释指定其名称。

如果不指定@Mapping,默认映射name相同的field
如果映射的对象field name不一样,通过 @Mapping 指定。
忽略字段加@Mapping#ignore() = true

  1. @Data
  2. public class Person {
  3. String describe;
  4. private String id;
  5. private String name;
  6. private int age;
  7. private BigDecimal source;
  8. private double height;
  9. private Date createTime;
  10. }
  11. @Data
  12. public class PersonDTO {
  13. String describe;
  14. private Long id;
  15. private String personName;
  16. private String age;
  17. private String source;
  18. private String height;
  19. }
  20. // mapper
  21. @Mapper
  22. public interface PersonMapper {
  23. PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
  24. @Mapping(target = "name", source = "personName")
  25. @Mapping(target = "id", ignore = true) // 忽略id,不进行映射
  26. PersonDTO conver(Person person);
  27. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

生成的实现类:

  1. public class PersonMapperImpl implements PersonMapper {
  2. public PersonMapperImpl() {
  3. }
  4. public PersonDTO conver(Person person) {
  5. if (person == null) {
  6. return null;
  7. } else {
  8. PersonDTO personDTO = new PersonDTO();
  9. personDTO.setDescribe(person.getDescribe());
  10. if (person.getId() != null) {
  11. personDTO.setId(Long.parseLong(person.getId()));
  12. }
  13. personDTO.setPersonName(person.getName());
  14. personDTO.setAge(String.valueOf(person.getAge()));
  15. if (person.getSource() != null) {
  16. personDTO.setSource(person.getSource().toString());
  17. }
  18. personDTO.setHeight(String.valueOf(person.getHeight()));
  19. return personDTO;
  20. }
  21. }
  22. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

测试:

  1. @Test
  2. public void test(){
  3. Person person = new Person();
  4. person.setDescribe("测试");
  5. person.setAge(18);
  6. person.setName("张三");
  7. person.setHeight(170.5);
  8. person.setSource(new BigDecimal("100"));
  9. PersonDTO dto = PersonMapper.INSTANCT.conver(person);
  10. System.out.println(dto);
  11. // PersonDTO(describe=测试, id=null, personName=张三, age=18, source=100, height=170.5)
  12. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2.2 指定默认值

在@Mapper接口类里面的转换方法上添加@Mapping注解
target() 必须添加,source()可以不添加,则直接使用defaultValue

  1. @Mapping(target = "describe", source = "describe", defaultValue = "默认值")
  2. PersonDTO conver(Person person);
  • 1
  • 2
  • 3

生成的impl:

  1. ...
  2. if (person.getDescribe() != null) {
  3. personDTO.setDescribe(person.getDescribe());
  4. } else {
  5. personDTO.setDescribe("默认值");
  6. }
  7. ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

测试:

  1. @Test
  2. public void test(){
  3. Person person = new Person();
  4. //person.setDescribe("测试");
  5. person.setAge(18);
  6. person.setName("张三");
  7. person.setHeight(170.5);
  8. person.setSource(new BigDecimal("100"));
  9. PersonDTO dto = PersonMapper.INSTANCT.conver(person);
  10. System.out.println(dto);
  11. // PersonDTO(describe=默认值, id=null, name=张三, age=18, source=100, height=170.5)
  12. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2.3 使用表达式

目前java是唯一受支持的语言,达式必须以Java表达式的形式给出
注意: 这个属性不能与source()、defaultValue()、defaultExpression()、qualifiedBy()、qualifiedByName()或constant()一起使用。

  1. @Mapping(target = "describe", source = "describe", defaultValue = "默认值")
  2. @Mapping(target = "createTime",expression = "java(new java.util.Date())")
  3. PersonDTO conver(Person person);
  • 1
  • 2
  • 3
  • 4

测试:

  1. @Test
  2. public void test(){
  3. Person person = new Person();
  4. //person.setDescribe("测试");
  5. person.setAge(18);
  6. person.setName("张三");
  7. person.setHeight(170.5);
  8. person.setSource(new BigDecimal("100"));
  9. PersonDTO dto = PersonMapper.INSTANCT.conver(person);
  10. System.out.println(dto);
  11. // PersonDTO(describe=默认值, id=null, name=张三, age=18, source=100, height=170.5, createTime=Fri Dec 11 23:21:31 GMT+08:00 2020)
  12. }

21d68bca8ad7f08ad1573432b310a03e.png

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

默认表达式@Mapping#defaultExpression()是默认值和表达式的组合。仅当source属性为null时才使用它们

2.4 dateFormat()

如果属性从字符串映射到日期,则该格式字符串可由SimpleDateFormat处理,反之亦然。当映射枚举常量时,将忽略所有其他属性类型。

  1. ....
  2. @Mapping(target = "createTime" ,source = "createTime", dateFormat = "yyyy-MM-dd")
  3. PersonDTO conver(Person person);
  4. ...
  • 1
  • 2
  • 3
  • 4
  • 5

impl:

  1. try {
  2. if (person.getCreateTime() != null) {
  3. personDTO.setCreateTime((new SimpleDateFormat("yyyy-MM-dd")).parse(person.getCreateTime()));
  4. }
  5. } catch (ParseException var4) {
  6. throw new RuntimeException(var4);
  7. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.5 组合映射

2.5.1 多个源对象

  1. @Data
  2. public class BasicEntity {
  3. private Date createTime;
  4. private String createBy;
  5. private Date updateTime;
  6. private String updateBy;
  7. private int _ROW;
  8. }
  9. @Mapper(uses =DateFormtUtil.class)
  10. public interface PersonMapper {
  11. PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
  12. @Mapping(target = "personName",source = "name")
  13. PersonDTO conver(Person person);
  14. @Mapping(target = "createTime",source = "basicEntity.createTime")
  15. PersonDTO combinationConver(Person personC, BasicEntity basicEntity);
  16. }

e9ad6ff3bca8f476ec02a9a08e8f23ca.png

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

2.5.2 使用其他的值

  1. ...
  2. @Mapping(target = "id", source = "id")
  3. PersonDTO mapTo(Person person, String id);
  4. ...
  • 1
  • 2
  • 3
  • 4
  • 5

虽然Person和Person有相同的id字段,但是映射器会使用mapTo方法里面的id参数。

2.6 嵌套映射

  1. @Data
  2. public class Person {
  3. ...
  4. private Child personChild;
  5. ...
  6. }
  7. @Data
  8. public class PersonDTO {
  9. ...
  10. private Child child;
  11. ...
  12. }
  13. // mapper
  14. @Mapper(uses =DateFormtUtil.class)
  15. public interface PersonMapper {
  16. PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
  17. @Mapping(target = "child", source = "personChild")
  18. PersonDTO conver(Person person);
  19. }

8a67939f05ed52649f3214d1ea13a48e.png

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

如果field name一样则不需要指定@Mapping

2.7 numberFormat()

如果带注释的方法从数字映射到字符串,则使用DecimalFormat将格式字符串作为可处理的格式。反之亦然。对于所有其他元素类型,将被忽略。
从基本2.1 基本映射可以看出,number类型与字符串直接的转换是通过valueOf(),如果字符串格式不正确会抛出java.lang.NumberFormatException异常,例如:Integer.valueOf(“10.2”)

使用numberFormat()之后DecimalFormat格式转换,还是会抛出NFE异常

  1. // mapper
  2. ....
  3. @Mapping(target = "age",source = "age", numberFormat = "#0.00")
  4. PersonDTO conver(Person person);
  5. ...
  6. // imppl
  7. personDTO.setAge((new DecimalFormat("#0.00")).format((long)person.getAge()));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.8 逆映射

在双向映射的情况下,例如从实体到DTO以及从DTO到实体,前向方法和反向方法的映射规则通常是相似的,并且可以通过切换source和来简单地反转target。

使用注释@InheritInverseConfiguration表示方法应继承相应反向方法的反向配置

  1. ....
  2. @Mapping(target = "age",source = "age", numberFormat = "#0.00")
  3. PersonDTO conver(Person person);
  4. @InheritInverseConfiguration
  5. Person conver(PersonDTO dto);
  6. ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.9 继承与共享配置

2.9.1 继承配置

方法级配置注解,例如@Mapping,@BeanMapping,@IterableMapping,等等,都可以继承从一个映射方法的类似使用注释方法@InheritConfiguration

  1. @Mapper
  2. public interface CarMapper {
  3. @Mapping(target = "numberOfSeats", source = "seatCount")
  4. Car carDtoToCar(CarDto car);
  5. @InheritConfiguration
  6. void carDtoIntoCar(CarDto carDto, @MappingTarget Car car);
  7. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上面的示例声明了一种carDtoToCar()具有配置的映射方法,该配置定义了应如何映射numberOfSeats类型中的属性Car。在现有Instance实例上执行映射的update方法Car需要相同的配置才能成功映射所有属性。通过声明@InheritConfiguration该方法,MapStruct可以搜索继承候选,以应用继承自该方法的注释。

如果所有类型的A(源类型和结果类型)都可以分配给B的相应类型,则一个方法A可以从另一种方法B继承配置。
如果可以使用多个方法作为继承的源,则必须在注释中指定方法名称:@InheritConfiguration( name = “carDtoToCar” )。

一种方法,可以使用==@InheritConfiguration==和覆盖或通过另外施加修改的配置@Mapping,@BeanMapping等等。

2.9.2 共享配置

MapStruct提供了通过指向带注释的中央接口来定义共享配置的可能性@MapperConfig。为了使映射器使用共享配置,需要在@Mapper#config属性中定义配置接口。

@MapperConfig注释具有相同的属性@Mapper注释。任何未通过via指定的属性@Mapper都将从共享配置中继承。中指定@Mapper的属性优先于通过引用的配置类指定的属性。列表属性例如uses可以简单组合:

  1. @MapperConfig(unmappedTargetPolicy = ReportingPolicy.ERROR )
  2. public interface CentralConfig {
  3. }
  4. @Mapper(config = CentralConfig.class } )
  5. // Effective configuration:
  6. // @Mapper(uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class },
  7. // unmappedTargetPolicy = ReportingPolicy.ERROR // )
  8. public interface SourceTargetMapper { ... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

共享配置config,配置一些检查策略
例如:

  1. unmappedSourcePolicy()、unmappedTargetPolicy() : 源或者目标没有标注映射的属性怎么报告
  2. typeConversionPolicy() :应该报告如何进行有损(缩小)转换,例如:long到integer的转换。
  3. collectionMappingStrategy(): 集合类型映射策略
    其他的,请阅读源码

3. 使用自定义方法

3.1 自定义类型转换方法

  1. public class DateMapper {
  2. public String asString(Date date) {
  3. return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
  4. .format( date ) : null;
  5. }
  6. public Date asDate(String date) {
  7. try {
  8. return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
  9. .parse( date ) : null;
  10. }
  11. catch ( ParseException e ) {
  12. throw new RuntimeException( e );
  13. }
  14. }
  15. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

mapper:

  1. @Mapper(uses=DateMapper.class)
  2. public interface PersonMapper{
  3. PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
  4. PersonDTO conver(Person person);
  5. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

impl:

  1. public class PersonMapperImpl implements PersonMapper {
  2. private final DateMapper dateMapper = new DateMapper();
  3. public PersonMapperImpl() {
  4. }
  5. public PersonDTO conver(Person person) {
  6. ....
  7. personDTO.setCreateTime(this.dateMapper.asDate(person.getCreateTime()));
  8. ...
  9. return personDTO;
  10. }
  11. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在进行类型转换的时候直接调用改转换方法
@Mapper#uses可以使用多个类

3.2 使用@Qualifier

@Qualifier标记的自定义注解标记的方法,必须有输入, 否则编译时会抛出异常

  1. public class DateFormtUtil {
  2. @DateFormat
  3. public static String dateToString(Date date){
  4. return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date);
  5. }
  6. @Qualifier
  7. @Target(ElementType.METHOD)
  8. @Retention(RetentionPolicy.CLASS)
  9. public @interface DateFormat{}
  10. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

mapper:

  1. @Mapper(uses =DateFormtUtil.class)
  2. public interface PersonMapper {
  3. PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
  4. @Mapping(target = "createTime",source = "createTime",qualifiedBy = DateFormat.class)
  5. PersonDTO conver(Person person);
  6. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3.3 使用@namd

  1. public class DateFormtUtil {
  2. @Named("dateToString")
  3. public static String dateToString(Date date){
  4. return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date);
  5. }
  6. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

mapper:

  1. @Mapper(uses =DateFormtUtil.class)
  2. public interface PersonMapper {
  3. PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
  4. @Mapping(target = "createTime",source = "createTime",qualifiedByName = "dateToString")
  5. PersonDTO conver(Person person);
  6. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

效果跟@Qualifier是一样的

4.集合映射

MapStructCollectionMappingStrategy,与可能的值:ACCESSOR_ONLY,SETTER_PREFERRED,ADDER_PREFERRED和TARGET_IMMUTABLE

在下表中,破折号-表示属性名称。接下来,尾部s表示复数形式。该表解释了这些选项以及它们是如何施加到存在/不存在的set-s,add-s和/或get-s在目标对象上的方法:














































选项 仅目标set-s可用 仅目标add-可用 既可以set-s/add- 没有set-s/add- 现有目标(@TargetType)
ACCESSOR_ONLY set-s get-s set-s get-s get-s
SETTER_PREFERRED set-s add- set-s get-s get-s
ADDER_PREFERRED set-s add- add- get-s get-s
TARGET_IMMUTABLE set-s exception set-s exception set-s

5.集成到 spring

@Mapper#componentModel 中指定依赖注入框架

  1. @Mapper(componentModel = "spring")
  2. public interface ModelMapper {
  3. ModelMapper INSTANT = Mappers.getMapper(ModelMapper.class);
  4. ModelVO conver(Model model);
  5. }
  6. // 直接在类中使用Autowired注入就行了
  7. @RestController
  8. class MapperSpringController {
  9. @Autowired
  10. ModelMapper modelMapper;
  11. @GetMapping("/get")
  12. ModelVO getModle(){
  13. Model model = new Model();
  14. model.setId("123456");
  15. model.setName("张三");
  16. model.setCreate(new Date());
  17. return modelMapper.conver(model);
  18. }
  19. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

6 高级运用

6.1 spi的运用

官方文档 关于spi的运用描述

6.2 freemarker生成代码

github链接

参考博客

————————————————
版权声明:本文为CSDN博主「清辉夜羽」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Z143430039/article/details/111054479

来源:【精选】Mapstruct 使用教程_胡八一 的博客-CSDN博客

发表评论

表情:
评论列表 (有 0 条评论,123人围观)

还没有评论,来说两句吧...

相关阅读

    相关 使用MapStruct映射集合

    1.映射集合 通常,使用MapStruct映射集合的方式与使用简单类型的方式相同。 基本上,必须创建一个简单的接口或抽象类并声明映射方法。 根据声明,MapStruct