要创建映射器,只需使用所需的映射方法定义一个 Java 接口并使用注释对其进行org.mapstruct.Mapper注释:
@Mapper
public interface CarMapper {
@Mapping(target = "manufacturer", source = "make")
@Mapping(target = "seatCount", source = "numberOfSeats")
CarDto carToCarDto(Car car);
@Mapping(target = "fullName", source = "name")
PersonDto personToPersonDto(Person person);
}
MapStruct 的一般理念是生成的代码看起来尽可能像您自己亲手编写的代码。特别是这意味着通过简单的 getter/setter 调用而不是反射或类似方法将值从源复制到目标。
MapStruct 还支持具有多个源参数的映射方法。这很有用,例如,为了将多个实体组合成一个数据传输对象。
@Mapper
public interface AddressMapper {
@Mapping(target = "description", source = "person.description")
@Mapping(target = "houseNumber", source = "address.houseNo")
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}
显示的映射方法采用两个源参数并返回一个组合的目标对象。与单参数映射方法一样,属性按名称映射。
如果多个源对象定义了具有相同名称的属性,则必须使用@Mapping注释指定从中检索属性的源参数,如示例中的description属性所示。当这种歧义未解决时,将引发错误。对于在给定源对象中只存在一次的属性,可以选择指定源参数的名称,因为它可以自动确定。
如果您不想显式命名嵌套源 bean 中的所有属性,则可以将其.用作目标。这将告诉 MapStruct 将每个属性从源 bean 映射到目标对象。
@Mapper
public interface CustomerMapper {
@Mapping( target = "name", source = "record.name" )
@Mapping( target = ".", source = "record" )
@Mapping( target = ".", source = "account" )
Customer customerDtoToCustomer(CustomerDto customerDto);
}
生成的代码将直接映射从CustomerDto.record到的每个属性Customer,无需手动命名它们中的任何一个,也是如此Customer.account。
当存在冲突时,可以通过显式定义映射来解决这些冲突。例如在上面的例子中。name发生在CustomerDto.record和 中CustomerDto.account。映射@Mapping( target = “name”, source = “record.name” )解决了这个冲突。
public class Customer {
private Long id;
private String name;
//getters and setter omitted for brevity
}
@Mapper
public interface CustomerMapper {
@Mapping(target = "name", source = "customerName")
Customer toCustomer(Map<String, String> map);
}
public class CustomerMapperImpl implements CustomerMapper {
@Override
public Customer toCustomer(Map<String, String> map) {
// ...
if ( map.containsKey( "id" ) ) {
customer.setId( Integer.parseInt( map.get( "id" ) ) );
}
if ( map.containsKey( "customerName" ) ) {
customer.setName( source.get( "customerName" ) );
}
// ...
}
}
源对象和目标对象中的映射属性并不总是具有相同的类型。例如,属性可能int属于源 bean 中的类型Long,但属于目标 bean 中的类型。
另一个例子是对其他对象的引用,这些对象应该映射到目标模型中的相应类型。例如,类Car可能具有在映射对象时需要转换为对象driver的类型的属性。PersonPersonDtoCar
在许多情况下,MapStruct 会自动处理类型转换。例如,如果一个属性int在源 bean 中属于类型但String在目标 bean 中属于类型,则生成的代码将分别通过调用String#valueOf(int)和透明地执行转换Integer#parseInt(String)(自动完成转化)
目前自动应用以下转换:
@Mapper
public interface CarMapper {
@Mapping(source = "price", numberFormat = "$#.00")
CarDto carToCarDto(Car car);
@IterableMapping(numberFormat = "$#.00")
List<String> prices(List<Integer> prices);
}
@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);
}
在最简单的情况下,嵌套级别上有一个属性需要更正。以fish在FishTankDto和 中具有相同名称的属性为例FishTank。对于这个属性 MapStruct 自动生成一个映射:FishDto fishToFishDto(Fish fish)。MapStruct 不可能知道有偏差的属性kind和type。因此,这可以在映射规则中解决:@Mapping(target=“fish.kind”, source=“fish.type”). 这告诉 MapStruct 偏离kind在此级别查找名称并将其映射到type.
@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 );
}
@Mapper
public interface FishTankMapperWithDocument {
@Mapping(target = "fish.kind", source = "fish.type")
@Mapping(target = "fish.name", expression = "java(\"Jaws\")")
@Mapping(target = "plant", ignore = true )
@Mapping(target = "ornament", ignore = true )
@Mapping(target = "material", ignore = true)
@Mapping(target = "quality.document", source = "quality.report")
@Mapping(target = "quality.document.organisation.name", constant = "NoIdeaInc" )
FishTankWithNestedDocumentDto map( FishTank source );
}
请注意@Mapping(target=“quality.document”, source=“quality.report”). DocumentDto在目标端不存在。它是从Report. MapStruct 继续在这里生成映射代码。该映射本身可以被引导到另一个名称。这甚至适用于常量和表达式。这在最后一个例子中显示:@Mapping(target=“quality.document.organisation.name”, constant=“NoIdeaInc”).
@Mapper
public interface CarMapper {
Set<String> integerSetToStringSet(Set<Integer> integers);
List<CarDto> carsToCarDtos(List<Car> cars);
CarDto carToCarDto(Car car);
}
生成的实现为每个元素integerSetToStringSet执行从Integer到到的转换String,而生成carsToCarDtos()的carToCarDto()方法为每个包含的元素调用方法,如下所示:
@Override
public Set<String> integerSetToStringSet(Set<Integer> integers) {
if ( integers == null ) {
return null;
}
Set<String> set = new LinkedHashSet<String>();
for ( Integer integer : integers ) {
set.add( String.valueOf( integer ) );
}
return set;
}
@Override
public List<CarDto> carsToCarDtos(List<Car> cars) {
if ( cars == null ) {
return null;
}
List<CarDto> list = new ArrayList<CarDto>();
for ( Car car : cars ) {
list.add( carToCarDto( car ) );
}
return list;
}//GENERATED CODE
carDto.setPassengers( personsToPersonDtos( car.getPassengers() ) );
...
在映射 bean 的集合类型属性时,例如从Car#passengers(类型List)到CarDto#passengers(类型List),MapStruct 将寻找具有匹配参数和返回类型的集合映射方法。
//GENERATED CODE
carDto.setPassengers( personsToPersonDtos( car.getPassengers() ) );
...
public interface SourceTargetMapper {
@MapMapping(valueDateFormat = "dd.MM.yyyy")
Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}
@Override
public Map<Long, Date> stringStringMapToLongDateMap(Map<String, String> source) {
if ( source == null ) {
return null;
}
Map<Long, Date> map = new LinkedHashMap<Long, Date>();
for ( Map.Entry<String, String> entry : source.entrySet() ) {
Long key = Long.parseLong( entry.getKey() );
Date value;
try {
value = new SimpleDateFormat( "dd.MM.yyyy" ).parse( entry.getValue() );
}
catch( ParseException e ) {
throw new RuntimeException( e );
}
map.put( key, value );
}
return map;
}
MapStruct 支持生成将一种 Java 枚举类型映射到另一种类型的方法。
默认情况下,源枚举中的每个常量都映射到目标枚举类型中具有相同名称的常量。如果需要,可以在@ValueMapping注释的帮助下将源枚举中的常量映射到具有另一个名称的常量。源枚举中的几个常量可以映射到目标类型中的同一个常量。
@Mapper
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class );
@ValueMappings({
@ValueMapping(target = "SPECIAL", source = "EXTRA"),
@ValueMapping(target = "DEFAULT", source = "STANDARD"),
@ValueMapping(target = "DEFAULT", source = "NORMAL")
})
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
}
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_;
}
}
@Mapper
public interface SpecialOrderMapper {
SpecialOrderMapper INSTANCE = Mappers.getMapper( SpecialOrderMapper.class );
@ValueMappings({
@ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ),
@ValueMapping( source = "STANDARD", target = MappingConstants.NULL ),
@ValueMapping( source = MappingConstants.ANY_REMAINING, target = "SPECIAL" )
})
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
}
public class SpecialOrderMapperImpl implements SpecialOrderMapper {
@Override
public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {
if ( orderType == null ) {
return ExternalOrderType.DEFAULT;
}
ExternalOrderType externalOrderType_;
switch ( orderType ) {
case STANDARD: externalOrderType_ = null;
break;
case RETAIL: externalOrderType_ = ExternalOrderType.RETAIL;
break;
case B2B: externalOrderType_ = ExternalOrderType.B2B;
break;
default: externalOrderType_ = ExternalOrderType.SPECIAL;
}
return externalOrderType_;
}
}
@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);
}
目前仅支持 Java 作为语言。例如,此功能可用于调用构造函数。整个源对象都可以在表达式中使用。应注意仅插入有效的 Java 代码:MapStruct 不会在生成时验证表达式,但在编译期间生成的类中会显示错误。
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target = "timeAndFormat",
expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )")
Target sourceToTarget(Source s);
}
源属性time和format组合成一个目标属性TimeAndFormat。请注意,指定了完全限定的包名称,因为 MapStruct 不负责TimeAndFormat类的导入(除非它在 中明确使用SourceTargetMapper)。
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);
}
默认表达式是默认值和表达式的组合。它们只会在源属性为null时使用
imports java.util.UUID;
@Mapper( imports = UUID.class )
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target="id", source="sourceId", defaultExpression = "java( UUID.randomUUID().toString() )")
Target sourceToTarget(Source s);
}
只在sourceId为空时为target中的id用java表达式赋值。
其他高阶用法请参考官方文档