在日常开发中,我们会定义多种不同的Javabean,比如DTO(Data Transfer Object:数据传输对象),DO(Data Object:数据库映射对象,与数据库一一映射),VO(View Object:显示层对象,通常是 Web 向模板渲染引擎层传输的对象)等等这些对象。在这些对象与对象之间转换通常是调对象的set和get方法进行复制,这种转换通常也是很无聊的操作,如果有一个专门的工具来解决Javabean之间的转换问题,让我们从这种无聊的转换操作中解放出来。
MapStruct就是这样一个属性映射工具,用于解决上述对象之间转换问题。MapStruct官网给出的定义:MapStruct是一个Java注释处理器,用于生成类型安全的bean映射类。
本篇文章主要用于记录自己整理的MapStruct使用教程。
简单使用
<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>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<version>4.12</version>
</dependency>
</dependencies>
<!-- 配置lombok 和mapStruct注解处理器 -->
<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>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
java代码如下:
定义Person实体
public class Person {
private String name;
private String lastName;
}
public class PersonDTO {
private String firstName;
private String lastName;
}
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
Person personDTOToPerson(PersonDTO personDTO);
}
使用上面定义的转换器,例子如下
public class PersonMapperTest {
public void personDTOToPerson() {
PersonMapper personMapper = PersonMapper.INSTANCE;
PersonDTO personDTO = new PersonDTO();
personDTO.setFirstName("feng");
personDTO.setLastName("xiu");
Person person = personMapper.personDTOToPerson(personDTO);
Assert.assertEquals(person.getLastName(),personDTO.getLastName());
Assert.assertEquals(person.getName(),personDTO.getFirstName());
}
}
定义Mapper(Bean映射器)
org.mapstruct.Mapper
注释对其进行注释。public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
Person personDTOToPerson(PersonDTO personDTO);
}
public class PersonMapperImpl implements PersonMapper {
public PersonMapperImpl() {
}
public Person personDTOToPerson(PersonDTO personDTO) {
if (personDTO == null) {
return null;
} else {
Person person = new Person();
person.setName(personDTO.getFirstName());
person.setLastName(personDTO.getLastName());
return person;
}
}
}
@Mapper(uses=XXXMapper.class)
来调用自定义的方法,这样可以方法重用。这个后面会说。@Mapper
public interface CarMapper {
@Mapping(...)
...
CarDto carToCarDto(Car car);
default PersonDto personToPersonDto(Person person) {
//hand-written mapping logic
}
}
@Mapper
public abstract class CarMapper {
@Mapping(...)
...
public abstract CarDto carToCarDto(Car car);
public PersonDto personToPersonDto(Person person) {
//hand-written mapping logic
}
}
public interface AddressMapper {
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}
public interface AddressMapper {
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn);
}
public interface CustomerMapper {
Customer customerDtoToCustomer(CustomerDto customerDto);
}
@Mapping( target = "name", source = "record.name" )
这样写.
代替,表示把对应属性bean匹配的内嵌属性映射到target上public interface CarMapper {
void updateCarFromDto(CarDto carDto, Car car);
}
public interface CarMapper {
Set<String> integerSetToStringSet(Set<Integer> integers);
List<CarDto> carsToCarDtos(List<Car> cars);
CarDto carToCarDto(Car car);
}
//GENERATED CODE
public Set<String> integerSetToStringSet(Set<Integer> integers) {
if ( integers == null ) {
return null;
}
Set<String> set = new HashSet<String>();
for ( Integer integer : integers ) {
set.add( String.valueOf( integer ) );
}
return set;
}
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;
}
@MapMapping
注解,用于处理value的转换public interface SourceTargetMapper {
@MapMapping(valueDateFormat = "dd.MM.yyyy")
Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}
//GENERATED CODE
@Override
public Map<Long, Date> stringStringMapToLongDateMap(Map<String, String> source) {
if ( source == null ) {
return null;
}
Map<Long, Date> map = new HashMap<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;
}
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class );
@ValueMapping(source = , , target = )
,
})
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
}
生成的代码如下
// GENERATED CODE
public class OrderMapperImpl implements OrderMapper {
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_;
}
}
MappingConstants.NULL : 处理null值,
MappingConstants.ANY_REMAINING : 处理所有未被定义或者名字匹配不上的
MappingConstants.ANY_UNMAPPED :处理任何违背匹配的情形
public interface TestMapper {
({
"able_status", target = "PERFECT"), (source =
"PASS"), (source = MappingConstants.NULL, target =
"failed_status", target = MappingConstants.NULL), (source =
"normal"), (source = MappingConstants.ANY_UNMAPPED, target =
})
String toEnum(DisableStatus disableStatus);
}
public class TestMapperImpl implements TestMapper {
public String toEnum(DisableStatus disableStatus) {
if ( disableStatus == null ) {
return "PASS";
}
String string;
switch ( disableStatus ) {
case able_status: string = "PERFECT";
break;
case failed_status: string = null;
break;
default: string = "normal";
}
return string;
}
}
public interface TestMapper {
({
"PERFECT", target = "able_status"), (source =
"PASS", target = MappingConstants.NULL), (source =
"failed_status"), (source = MappingConstants.NULL, target =
"normal_status"), (source = MappingConstants.ANY_UNMAPPED, target =
})
DisableStatus toEnum(String disableStatus);
}
public class TestMapperImpl implements TestMapper {
public DisableStatus toEnum(String disableStatus) {
if ( disableStatus == null ) {
return DisableStatus.failed_status;
}
DisableStatus disableStatus1;
switch ( disableStatus ) {
case "PERFECT": disableStatus1 = DisableStatus.able_status;
break;
case "PASS": disableStatus1 = null;
break;
default: disableStatus1 = DisableStatus.normal_status;
}
return disableStatus1;
}
}
public interface TestMapper {
({
"PERFECT", target = "able_status"), (source =
"PASS", target = MappingConstants.NULL), (source =
"failed_status"), (source = MappingConstants.NULL, target =
"normal_status"), (source = MappingConstants.ANY_REMAINING, target =
})
DisableStatus toEnum(String disableStatus);
}
public class TestMapperImpl implements TestMapper {
public DisableStatus toEnum(String disableStatus) {
if ( disableStatus == null ) {
return DisableStatus.failed_status;
}
DisableStatus disableStatus1;
switch ( disableStatus ) {
case "PERFECT": disableStatus1 = DisableStatus.able_status;
break;
case "PASS": disableStatus1 = null;
break;
case "able_status": disableStatus1 = DisableStatus.able_status;
break;
case "disable_status": disableStatus1 = DisableStatus.disable_status;
break;
case "normal_status": disableStatus1 = DisableStatus.normal_status;
break;
case "failed_status": disableStatus1 = DisableStatus.failed_status;
break;
case "ok_status": disableStatus1 = DisableStatus.ok_status;
break;
case "fine_status": disableStatus1 = DisableStatus.fine_status;
break;
default: disableStatus1 = DisableStatus.normal_status;
}
return disableStatus1;
}
}
自定义名称转换
public enum LevelEnum {
able(1, "完美"),
disable(2, "合格"),
normal(3, "普通"),
failed(4, "不及格"),
ok(5, "还行"),
fine(6, "可以");
private Integer code;
private String desc;
LevelEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
public enum DisableStatus {
able_status(1, "完美"),
disable_status(2, "合格"),
normal_status(3, "普通"),
failed_status(4, "不及格"),
ok_status(5, "还行"),
fine_status(6, "可以");
private Integer code;
private String desc;
DisableStatus(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}
public interface TestMapper {
"stripSuffix", configuration = "_status") (nameTransformationStrategy =
LevelEnum toEnum(DisableStatus disableStatus);
}
public class TestMapperImpl implements TestMapper {
public LevelEnum toEnum(DisableStatus disableStatus) {
if ( disableStatus == null ) {
return null;
}
LevelEnum levelEnum;
switch ( disableStatus ) {
case able_status: levelEnum = LevelEnum.able;
break;
case disable_status: levelEnum = LevelEnum.disable;
break;
case normal_status: levelEnum = LevelEnum.normal;
break;
case failed_status: levelEnum = LevelEnum.failed;
break;
case ok_status: levelEnum = LevelEnum.ok;
break;
case fine_status: levelEnum = LevelEnum.fine;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + disableStatus );
}
return levelEnum;
}
}
检索映射器
org.mapstruct.factory.Mappers
。只需要调用getMapper方法,传递接口类型的mapper就可以获得MapStruct自动生成的Mapperpublic interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
CarDto carToCarDto(Car car);
}
"spring") (componentModel =
public interface CarMapper {
CarDto carToCarDto(Car car);
}
@AutoWired
private CarMapper mapper;
"spring", uses = EngineMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR) (componentModel =
public interface CarMapper {
CarDto carToCarDto(Car car);
}
InjectionStrategy#CONSTRUCTOR
,构造函数将具有适当的注解,而字段则没有。当使用InjectionStrategy#FIELD
,注解字段位于field本身。目前,默认的注入策略是field注入。建议使用构造函数注入来简化测试。{@code@Named}
注释,可以通过@Inject检索,总结
作者|枫秀
编辑|橙子君
出品|阿里巴巴新零售淘系技术