Springboot2.X 集成MapStruct

1,680 阅读2分钟

一、MapStruct使用

MapStruct是一种类型安全的bean映射类生成java注释处理器。我们要做的就是定义一个映射器接口,声明任何必需的映射方法。在编译的过程中,MapStruct注解处理器(mapstruct-processor)会在target/generated-sources/annotations下会生成@Mapper接口的实现类。

使用mapstruct需要mapstruct依赖和mapstruct-processor注解处理器的依赖,使用Maven引入依赖一共有两种方式:

以插件的形式添加mapstruct-processor 普通使用可参考 Mapstruct官方指导。

<properties>
    <org.mapstruct.version>1.3.1.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.5.1</version> <!-- or newer 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>${org.mapstruct.version}</version>
                    </path>
                    <!-- other annotation processors -->
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

需要注意的是,项目中如果使用lombok,需要在编译插件中也配置lombok。否者可能会导致无法正常生成@Mapper接口的实现类。

             <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler-plugin.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${mapstruct.version}</version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>

注意:maven-comiler-plugin插件版本一定要在3.6.0以上,若版本低,则会报找不到属性的错误 如果项目中用到swagger需要在有依赖swagger的地方:最好使用maven help 进行包管理

<exclusions>
    <exclusion>
        <artifactId>mapstruct</artifactId>
        <groupId>org.mapstruct</groupId>
    </exclusion>
</exclusions>

否则会报: Couldn’t retrieve @Mapper annotation

二、mapstruct 字段映射

基本映射

@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

    @Mapping(source = "make", target = "manufacturer")
    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDto carToCarDto(Car car);
	
    @Mapping(source = "name", target = "fullName")
    PersonDto personToPersonDto(Person person);
}

代码中使用CarDTO carDTO = CarMapper.INSTANCE.carToCarDto(car)进行类型抓换(下同)。 在生成的方法实现中,源类型(例如Car)的所有可读属性都将被复制到目标类型(例如CarDto)的相应属性中: 当一个属性与其目标实体对应的名称相同时,它将被隐式映射。 当属性在目标实体中具有不同的名称时,可以通过@Mapping注释指定其名称。

多个属性的映射

场景:为了将多个实体组合到一个数据传输对象中。

@Mapper
public interface AddressMapper {
	AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);
	
    @Mapping(source = "person.description", target = "description")
    @Mapping(source = "address.houseNo", target = "houseNumber")
    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}

直接引用源参数的映射方法

@Mapper
public interface AddressMapper {
	AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);
	
    @Mapping(source = "person.description", target = "description")
    @Mapping(source = "address.houseNo", target = "houseNumber")
    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}

继承反转配置

场景:已经存在一个Car映射成CarDTO的方法,现在需要将CarDTO映射成Car。

@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
	   
    @Mapping(source = "make", target = "manufacturer")
    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDto carToCarDto(Car car);

	@InheritInverseConfiguration(name = "carToCarDto")
	Car carDTOTocar(CarDto carDto);
}

默认值和常量

分别可以通过@Mapping的defaultValue和constant属性指定,当source对象的属性值为null时,如果有指定defaultValue将注入defaultValue的设定的值。constant属性通用用于给target属性注入常量值。

 @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);
}

如果为s.getStringProp() == null,则将target属性stringProperty设置为"undefined"而不是应用来自s.getStringProp()的值。如果为s.getLongProperty() == null,则目标属性longProperty将设置为-1。将String "Constant Value"设置为目标属性stringConstant。该值"3001"被类型转换为 targe类的longWrapperConstant属性。该常量"jack-jill-tom"将破折号分隔的列表映射到List。

日期类型

 @Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss", expression = "java(new Date())")
 CarDTO doToDTO(CarDO carDO);

数字类型

从int到String的转换

@Mapper
public interface CarMapper {

    @Mapping(source = "price", numberFormat = "$#.00")
    CarDto carToCarDto(Car car);

    @IterableMapping(numberFormat = "$#.00")
    List<String> prices(List<Integer> prices);
}

从BigDecimal到String的转换

@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);
}

控制嵌套bean映射

场景:对象内部存在多重嵌套。

@Mapper
public interface FishTankMapper {
    @Mapping(target = "fish.kind", source = "fish.type")
    @Mapping(target = "fish.name", ignore = true)
    @Mapping(target = "ornament", source = "interior.ornament")
    @Mapping(target = "material.materialType", source = "material")
    @Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName")
    FishTankDto map( FishTank source );
}

映射表达式

场景:target属性需要通过计算得到。这个功能很强大,可以适用很多场景,比如将Integer类转换成枚举类,单位换算等等。

@Mappings({
            @Mapping(target = "extensionInsuranceAmount", expression = "java(com.souche.connector.common.util.AmountConvertUtil.convertToFen(vo.getExtensionInsuranceAmount()))"),
            @Mapping(target = "insuranceAmount", expression = "java(com.souche.connector.common.util.AmountConvertUtil.convertToFen(vo.getInsuranceAmount()))"),
            @Mapping(target = "purchaseTax", expression = "java(com.souche.connector.common.util.AmountConvertUtil.convertToFen(vo.getPurchaseTax()))"),
            @Mapping(target = "decorationAmount", expression = "java(com.souche.connector.common.util.AmountConvertUtil.convertToFen(vo.getDecorationAmount()))"),
            @Mapping(target = "boutiqueAmount", expression = "java(com.souche.connector.common.util.AmountConvertUtil.convertToFen(vo.getBoutiqueAmount()))"),
            @Mapping(target = "maintainPackageAmount", expression = "java(com.souche.connector.common.util.AmountConvertUtil.convertToFen(vo.getMaintainPackageAmount()))"),
            @Mapping(target = "repairAmount", expression = "java(com.souche.connector.common.util.AmountConvertUtil.convertToFen(vo.getRepairAmount()))"),
            @Mapping(target = "renewalFundAmount", expression = "java(com.souche.connector.common.util.AmountConvertUtil.convertToFen(vo.getRenewalFundAmount()))")
    })
    AdditionalDetailDTO map(AdditionalDetailVO vo);

映射集合

场景:将一个集合映射成另外一个集合。

@Mapper
public interface CarMapper {

    Set<String> integerSetToStringSet(Set<Integer> integers);

    List<CarDto> carsToCarDtos(List<Car> cars);

    CarDto carToCarDto(Car car);
}

映射map

场景:将一个Map对象映射成另外一个Map对象

public interface SourceTargetMapper {

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

映射枚举类型

可以在@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);
}