在我们项目中,我们经常要处理将DTO转换成VO,DTO转成Entity等各类对象相互转换,如果我们采用BeanUtils工具类的copyProperty进行转换,很容易出现转换性能低,类型转换错误等问题。
与其他转换工具相对,MapStruct具有以下优点:
通过使用普通方法调用而不是反射来快速执行
编译时类型安全性:只能映射相互映射的对象和属性,不能将订单实体意外映射到客户DTO等。
...
<properties>
<org.mapstruct.version>1.3.1.Final</org.mapstruct.version>
</properties>
...
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<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>
<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>
...
场景:将一个对象映射成另外一种类型的对象,如将DTO映射成VO
定义一个映射接口类CarMapper
@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);
}
场景:将一个Bean的属性更新到另外一个bean的同名属性
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
}
场景:已经存在一个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);
}
在许多情况下,MapStruct会自动处理类型转换。例如,如果一个属性int在源Bean中是类型但String在目标Bean中是类型,则生成的代码将分别通过分别调用String#valueOf(int)和来透明地执行转换Integer#parseInt(String)。
当前,以下转换将自动应用:
之间的所有Java基本数据类型及其相应的包装类型,例如之间int和Integer,boolean和Boolean等。之间的所有Java基本类型和包装类之间,例如int和long或byte和Integer。
从较大的数据类型转换为较小的数据类型(例如从long到int)可能会导致值或精度损失。在Mapper和MapperConfig注释有一个方法typeConversionPolicy来控制警告/错误。由于向后兼容的原因,默认值为“ ReportingPolicy.IGNORE”。
所有Java基本类型之间(包括其包装)和String之间,例如int和String或Boolean和String。java.text.DecimalFormat可以指定理解的格式字符串。
从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 = "power", numberFormat = "#.##E0")
CarDto carToCarDto(Car car);
}
从日期到字符串的转换
@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);
}
更多映射见@Mapping注解
场景:对象内部存在多重嵌套。
@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对象
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);
}
分别可以通过@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。