暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Java实体映射工具-MapStruct

Hans213 2021-09-27
894


MapStruct 是一个用于生成类型安全,高性能,无依赖的bean映射代码的注释处理器

本文将介绍mapstruct的基本使用,并提供对应的示例

1. 简介

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

使用时只需要定义一个mapper接口,在该接口声明所需的映射方法。在编译期间,MapStruct将生成此接口的实现类。

与动态映射框架相比,MapStruct具有以下优势:

  • 快速,原理是生成普通方法调用而不是反射

  • 编译时类型安全:只能映射相互映射的对象和属性

  • 在构建时清除错误报告,如果

    • 映射不完整(并非所有目标属性都已映射)

    • 映射不正确(找不到合适的映射方法或类型转换)


2. 设置


MapStruct 包含以下组件

  • org.mapstruct:mapstruct:包含所需的注释,例如@Mapping

  • org.mapstruct:mapstruct-processor:包含生成映射器实现的注释处理器


2.1 Maven

MapStruct Maven配置

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

    2.2 配置选项

    可以使用 注释处理器选项  配置MapStruct代码生成器。

    MapStruct 配置选项示例

      ...
      <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>${org.mapstruct.version}</version>
      </path>
      </annotationProcessorPaths>
      <compilerArgs>
      <compilerArg>
      -Amapstruct.suppressGeneratorTimestamp=true
      </compilerArg>
      <compilerArg>
      -Amapstruct.suppressGeneratorVersionInfoComment=true
      </compilerArg>
      </compilerArgs>
      </configuration>
      </plugin>
      ...


      MapStruct 处理器配置选项

      选项目的默认
      mapstruct. suppressGeneratorTimestamp
      如果设置为true
      ,则不会在生成的映射器类的注释中创建时间戳。
      false
      mapstruct. suppressGeneratorVersionInfoComment
      如果设置为true
      ,则不会在生成的映射器类comment
      中创建版本及编译器信息。
      false
      mapstruct.defaultComponentModel
      组件模型的名称(请参阅检索映射器)基于应生成的映射器。支持的值有:default
      cdi
      spring
      jsr330
      default
      mapstruct.unmappedTargetPolicy
      如果未使用源值填充映射方法的目标对象的属性,则应用默认报告策略。支持的值是:ERROR
      :任何未映射的目标属性都将导致映射代码生成失败WARN
      :任何未映射的目标属性都会在构建时发出警告IGNORE
      :忽略未映射的目标属性如果为特定映射器via提供了策略@Mapper#unmappedTargetPolicy()
      ,则注释中的值优先。
      WARN


      2.3 与Lombok一起使用

      https://github.com/mapstruct/mapstruct-examples/blob/master/mapstruct-lombok/pom.xml

      与Lombok一起使用Maven配置

        <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
        <org.projectlombok.version>1.18.12</org.projectlombok.version>
        <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
        </properties>

        <dependencies>

        <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
        </dependency>

        <!-- lombok dependencies should not end up on classpath -->
        <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${org.projectlombok.version}</version>
        <scope>provided</scope>
        </dependency>

        <!-- IntelliJ pre 2018.1.1 requires the mapstruct processor to be present as provided dependency -->
        <!-- <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>${org.mapstruct.version}</version>
        <scope>provided</scope>
        </dependency>-->

        <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
        <version>4.13.1</version>
        </dependency>
        </dependencies>

        <build>
        <pluginManagement>
        <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>${org.mapstruct.version}</version>
        </path>
        <path>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${org.projectlombok.version}</version>
        </path>
        <path>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok-mapstruct-binding</artifactId>
        <version>${lombok-mapstruct-binding.version}</version>
        </path>
        </annotationProcessorPaths>
        </configuration>
        </plugin>
        </plugins>
        </pluginManagement>
        </build>


        2.4 使用低版本

        实际开发中,遇到 SpringBoot 1.5.7 RELEASE 版本的项目可以通过如下配置

        低版本Maven配置

          <properties>
          <org.mapstruct.version>1.0.0.Final</org.mapstruct.version>
          </properties>

          <dependencies>

          <dependency>
          <groupId>org.mapstruct</groupId>
          <artifactId>mapstruct-jdk8</artifactId>
          <version>${org.mapstruct.version}</version>
          </dependency>

          </dependencies>

          <build>
          <plugins>

          <plugin>
          <groupId>org.bsc.maven</groupId>
          <artifactId>maven-processor-plugin</artifactId>
          <version>2.2.4</version>
          <configuration>
          <defaultOutputDirectory>
          ${project.build.directory}/generated-sources
          </defaultOutputDirectory>
          <processors>
          <processor>org.mapstruct.ap.MappingProcessor</processor>
          </processors>
          </configuration>
          <executions>
          <execution>
          <id>process</id>
          <phase>generate-sources</phase>
          <goals>
          <goal>process</goal>
          </goals>
          </execution>
          </executions>
          <dependencies>
          <dependency>
          <groupId>org.mapstruct</groupId>
          <artifactId>mapstruct-processor</artifactId>
          <version>${org.mapstruct.version}</version>
          </dependency>
          </dependencies>
          </plugin>

          </plugins>

          </build>


          3. 定义映射器


          3.1 基本映射

            @Mapper
            public interface CarMapper {

            /**
            * CarMapper 实例
            */
            CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

            /**
            * car转carDto
            *
            * @param car 汽车
            * @return carDto
            */
            @Mapping(source = "make", target = "manufacturer")
            @Mapping(source = "numberOfSeats", target = "seatCount")
            CarDto carToCarDTO(Car car);

            /**
            * person 转 personDto
            * @param person source
            * @return personDto
            */
            @Mapping(source = "name", target = "fullName")
            PersonDto personToPersonDto(Person person);

            }

            // 使用Mapper
            CarDto carDto = CarMapper.INSTANCE.carToCarDTO(car);

            接口也可以替换成抽象类,在抽象类中定义抽象方法

              @Mapper
              public abstract class CarMapper {

              /**
              * CarMapper 实例
              */
              CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

              /**
              * car转carDto
              *
              * @param car 汽车
              * @return carDto
              */
              @Mapping(source = "make", target = "manufacturer")
              @Mapping(source = "numberOfSeats", target = "seatCount")
              public abstract CarDto carToCarDTO(Car car);

              /**
              * person 转 personDto
              * @param person source
              * @return personDto
              */
              @Mapping(source = "name", target = "fullName")
              public abstract PersonDto personToPersonDto(Person person);

              }

              3.2 添加自定义方法

              如果我们遇到一些无法用MapStruct无法生成处理的逻辑,可以在接口中定义默认方法。

                @Mapper
                public interface CarMapper {

                @Mapping(...)
                ...
                CarDto carToCarDto(Car car);

                default PersonDto personToPersonDto(Person person) {
                //hand-written mapping logic
                }
                }


                3.3 多个数据源参数的映射方法

                主要解决将多个实体组合成一个数据传输对象的场景

                   @Mapper
                  public interface AddressMapper {

                  @Mapping(source = "person.description", target = "description")
                  @Mapping(source = "address.houseNo", target = "houseNumber")
                  DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
                  }

                  3.4 更新现有bean实例

                  有些场景需要传入目标对象,而不是新建,可用通过为目标对象添加参数并使用 @MappingTarget
                  标记来实现。方法的返回值可以是void
                  ,也可以是Car(此时会返回更新后的对象)。

                     @Mapper
                    public interface CarMapper {

                    void updateCarFromDto(CarDto carDto, @MappingTarget Car car);

                    }


                    4. 检索映射器

                    4.1 Mappers工厂

                       CarMapper mapper = Mappers.getMapper( CarMapper.class );


                      4.2 依赖注入

                         @Mapper(componentModel = "spring")
                        public interface CarMapper {

                        CarDto carToCarDto(Car car);

                        }

                        组件模型:

                        • default
                          :映射器不使用组件模型,通常通过检索实例 Mappers#getMapper(Class)

                        • cdi
                          :生成的映射器是一个应用程序范围的CDI bean,可以通过它检索 @Inject

                        • spring
                          :生成的映射器是一个单例范围的Spring bean,可以通过它检索 @Autowired

                        • jsr330
                          :生成的映射器使用{@code @Named}进行注释,并且可以通过@Inject
                          (例如使用Spring)进行检索


                        5. 数据类型转换


                        5.1 隐式类型转换

                        在许多情况下,MapStruct会自动处理类型转换。

                        • Java基本数据类型及其相应的包装类型,例如之间int
                          Integer
                          boolean
                          Boolean

                        • 所有Java基本类型之间(包括其包装)和String
                          之间

                        • enum
                          类型和之间String

                        • 在大数字类型(java.math.BigInteger
                          java.math.BigDecimal
                          )和Java原始类型(包括它们的包装器)以及String之间。java.text.DecimalFormat
                          可以指定理解的格式字符串

                           @Mapper
                          public interface CarMapper {

                          @Mapping(source = "power", numberFormat = "#.##E0")
                          CarDto carToCarDto(Car car);

                          }
                          • java.util.Date
                            / XMLGregorianCalendar
                            String
                            之间。java.text.SimpleDateFormat
                            可以通过dateFormat
                            选项指定理解的格式字符串

                             @Mapper
                            public interface CarMapper {

                            @Mapping(source = "manufacturingDate", dateFormat = "dd.MM.yyyy")
                            CarDto carToCarDto(Car car);

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

                            5.2 调用其他映射器

                            除了在同一映射器类型上定义的方法之外,MapStruct还可以调用其他类中定义的映射方法,无论是MapStruct生成的映射器还是手写映射方法。

                              public class DateMapper {

                              public String asString(Date date) {
                              return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
                              .format( date ) : null;
                              }

                              public Date asDate(String date) {
                              try {
                              return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
                              .parse( date ) : null;
                              }
                              catch ( ParseException e ) {
                              throw new RuntimeException( e );
                              }
                              }
                              }

                              // 引用另一个映射器
                              @Mapper(uses=DateMapper.class)
                              public class CarMapper {

                              CarDto carToCarDto(Car car);
                              }

                              6. 其他映射

                              映射也支持集合 和 流 作为入参,这里暂不总结

                              6.1 映射枚举类型


                              默认情况下,源枚举中的每个常量都映射到目标枚举类型中具有相同名称的常量。如果需要,可以借助@ValueMapping
                              注释将源枚举中的常量映射到具有其他名称的常量。

                                @Mapper
                                public interface OrderMapper {

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

                                @ValueMappings({
                                @ValueMapping(source = "EXTRA", target = "SPECIAL"),
                                @ValueMapping(source = "STANDARD", target = "DEFAULT"),
                                @ValueMapping(source = "NORMAL", target = "DEFAULT")
                                })
                                ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
                                }


                                生成的代码

                                  // GENERATED CODE
                                  public class OrderMapperImpl implements OrderMapper {

                                  @Override
                                  public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {
                                  if ( orderType == null ) {
                                  return null;
                                  }

                                  ExternalOrderType externalOrderType_;

                                  switch ( orderType ) {
                                  case EXTRA: externalOrderType_ = ExternalOrderType.SPECIAL;
                                  break;
                                  case STANDARD: externalOrderType_ = ExternalOrderType.DEFAULT;
                                  break;
                                  case NORMAL: externalOrderType_ = ExternalOrderType.DEFAULT;
                                  break;
                                  case RETAIL: externalOrderType_ = ExternalOrderType.RETAIL;
                                  break;
                                  case B2B: externalOrderType_ = ExternalOrderType.B2B;
                                  break;
                                  default: throw new IllegalArgumentException( "Unexpected enum constant: " + orderType );
                                  }

                                  return externalOrderType_;
                                  }
                                  }

                                  7. 高级映射选项

                                  7.1 默认值和常量

                                    @Mapper(uses = StringListMapper.class)
                                    public interface SourceTargetMapper {

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

                                    @Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")
                                    @Mapping(target = "longProperty", source = "longProp", defaultValue = "-1")
                                    @Mapping(target = "stringConstant", constant = "Constant Value")
                                    @Mapping(target = "integerConstant", constant = "14")
                                    @Mapping(target = "longWrapperConstant", constant = "3001")
                                    @Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014")
                                    @Mapping(target = "stringListConstants", constant = "jack-jill-tom")
                                    Target sourceToTarget(Source s);
                                    }


                                    • defaultValue
                                      String
                                      类型,其中包含了隐式转换

                                    • 如果是日期或者数字,需要指定对应的 dateFormat
                                      numberFormat

                                    • 常量"jack-jill-tom"
                                      演示了如何调用手写类 StringListMapper
                                      来将以破折号分隔的列表映射到a List<String>

                                    7.2 表达式和默认表达式

                                    目前只支持Java作为语言。可以在@Mapper
                                    中加入 imports
                                    导入对应的 package

                                    使用表达式示例

                                       imports org.sample.TimeAndFormat;

                                      @Mapper( imports = TimeAndFormat.class )
                                      public interface SourceTargetMapper {

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

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



                                      使用默认表达式示例

                                        imports java.util.UUID;

                                        @Mapper( imports = UUID.class )
                                        public interface SourceTargetMapper {

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

                                        // 如果sourceId为null,则生成UUID
                                        @Mapping(target="id", source="sourceId", defaultExpression = "java( UUID.randomUUID().toString() )")
                                        Target sourceToTarget(Source s);
                                        }

                                        7.3 确定结果类型

                                        当结果类型具有继承关系时,可通过以下方式解决

                                          @Mapper( uses = FruitFactory.class )
                                          public interface FruitMapper {

                                          @BeanMapping( resultType = Apple.class )
                                          Fruit map( FruitDto source );

                                          }

                                          // 使用的自定义类
                                          public class FruitFactory {

                                          public Apple createApple() {
                                          return new Apple( "Apple" );
                                          }

                                          public Banana createBanana() {
                                          return new Banana( "Banana" );
                                          }
                                          }


                                          7.4 关于 null 参数的映射结果

                                          可以指定不同的策略,这里使用到了再来总结

                                          7.5 异常的处理

                                          如果使用的自定义映射类中方法定义了异常

                                            @Mapper(uses = HandWritten.class)
                                            public interface CarMapper {

                                            CarDto carToCarDto(Car car) throws GearException;
                                            }

                                            // HandWritten 中自定义的方法,抛出异常
                                            public class HandWritten {

                                            private static final String[] GEAR = {"ONE", "TWO", "THREE", "OVERDRIVE", "REVERSE"};

                                            public String toGear(Integer gear) throws GearException, FatalException {
                                            if ( gear == null ) {
                                            throw new FatalException("null is not a valid gear");
                                            }

                                            if ( gear < 0 && gear > GEAR.length ) {
                                            throw new GearException("invalid gear");
                                            }
                                            return GEAR[gear];
                                            }
                                            }



                                            8. 重用映射配置

                                            8.1 逆映射

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

                                               @Mapper
                                              public interface CarMapper {

                                              @Mapping(source = "numberOfSeats", target = "seatCount")
                                              CarDto carToDto(Car car);

                                              @InheritInverseConfiguration
                                              @Mapping(target = "numberOfSeats", ignore = true)
                                              Car carDtoToCar(CarDto carDto);
                                              }



                                              9. 自定义映射


                                              9.1 使用装饰器映射自定义

                                              在某些情况下,可能需要定制生成的映射方法,例如,在目标对象中设置不能由生成的方法实现设置的附加属性。MapStruct使用装饰器支持此要求。

                                                // 应用装饰器
                                                @Mapper
                                                @DecoratedWith(PersonMapperDecorator.class)
                                                public interface PersonMapper {


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


                                                PersonDto personToPersonDto(Person person);


                                                AddressDto addressToAddressDto(Address address);
                                                }




                                                // 装饰器必须是装饰映射器类型的子类型
                                                public abstract class PersonMapperDecorator implements PersonMapper {


                                                private final PersonMapper delegate;


                                                public PersonMapperDecorator(PersonMapper delegate) {
                                                this.delegate = delegate;
                                                }


                                                @Override
                                                public PersonDto personToPersonDto(Person person) {
                                                PersonDto dto = delegate.personToPersonDto( person );
                                                dto.setFullName( person.getFirstName() + " " + person.getLastName() );
                                                return dto;
                                                }
                                                }


                                                如果使用 componentModel="spring" 的装饰器, 扩展装饰器的生成类, 会使用Spring的@Primary
                                                注释进行注释

                                                  public abstract class PersonMapperDecorator implements PersonMapper {

                                                  @Autowired
                                                  @Qualifier("delegate")
                                                  private PersonMapper delegate;

                                                  @Override
                                                  public PersonDto personToPersonDto(Person person) {
                                                  PersonDto dto = delegate.personToPersonDto( person );
                                                  dto.setName( person.getFirstName() + " " + person.getLastName() );

                                                  return dto;
                                                  }
                                                  }

                                                  // 使用装饰器
                                                  @Autowired
                                                  private PersonMapper personMapper;


                                                  参考文章

                                                  • github:

                                                    • Github https://github.com/mapstruct/mapstruct/

                                                    • 示例 https://github.com/mapstruct/mapstruct-examples

                                                  • 官方指南(中文):http://www.kailing.pub/MapStruct1.3/index.html

                                                  • 官方指南:https://mapstruct.org/documentation/stable/reference/html/

                                                  文章转载自Hans213,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                                  评论