概述
针对模型转换,我们常用的方式是
方式一:BeanUtils.copyProperties(source, target);
方式二:自己写converter方法。
下面讲一个目前主流推荐的做法:使用mapstruct
。
mapstruct
本质就是自动帮我们生成转换代码。我们只需要配置好策略即可。
依赖
...
<properties>
<org.mapstruct.version>1.5.3.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>
<!-- 注意顺序,lombok在前,mapstruct在后 -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
定义mapper
为了告诉mapstruct
怎么生成转换代码,我们需要先定义个接口。官方把这个操作称为:定义mapper。(创建映射器)
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
// 模型转换
UnityDTO modelToDTO(UnityModel unityModel);
}
两个模型如下:
@Data
public class UnityDTO {
/**
* 纠纷单Id
*/
private String disputeOrderId;
/**
* 订单Id
*/
private Long orderId;
}
@Data
public class UnityModel {
/**
* 纠纷单Id
*/
private String disputeOrderId;
/**
* 订单Id
*/
private Long orderId;
}
这个时候只需要,编辑下项目,mapstruct就会帮我们把转换代码生成出来。生成的代码在target/classes
文件夹里。
如果是简单使用,理论上来说,其实已经结束了。不过只是简单的使用肯定满足不了我们的需求。
字段名不一致的情况
简单情况
如果两个字段名不一致,如下,一个orderId,一个是oId。
@Data
public class UnityDTO {
/**
* 纠纷单Id
*/
private String disputeOrderId;
/**
* 订单Id
*/
private Long orderId;
}
@Data
public class UnityModel {
/**
* 纠纷单Id
*/
private String disputeOrderId;
/**
* 订单Id
*/
private Long oId;
}
指定映射字段:
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
// 模型转换
@Mapping(target = "orderId", source = "oId")
UnityDTO modelToDTO(UnityModel unityModel);
}
复杂情况
expression
下面情况是:model里面平铺的字段,转换到DTO中时,希望能放到extInfo的map中:
public class UnityModel {
private String reverseBizType;
private String reverseOrderBizType;
}
public class UnityDTO {
/**
* 纠纷单Id
*/
private Map<String, String> extInfo;
}
/**
* 字段不同时的指定方式,多个字段不同,写多个@Mapping即可
*/
@Mappings({
@Mapping(target = "extInfo", expression = "java(INSTANCE.fillExtInfo(unityModel))")
})
UnityDTO modelToDTO(UnityModel unityModel);
default Map<String, String> fillExtInfo(JudgementUnityModel unityModel) {
Map<String, String> map = new HashMap<>();
Map<String, String> extInfo = unityModel.getExtInfo();
if (extInfo != null) {
map.putAll(extInfo);
}
map.put("reverseBizType", unityModel.getReverseBizType());
map.put("reverseOrderBizType", unityModel.getReverseOrderBizType());
return map;
}
这里是利用expression
的能力,单独调用fillExtInfo方法。
具体步骤:
- 使用
@Mapping(target = "目标字段名", expression = "表达式")
- 编写表达式中的方法,入参为
source
模型。
afterMapping
除了使用expression
的方式之外,还可以使用@AfterMapping
@AfterMapping
default void fillExtInfo(@MappingTarget JudgementUnityMqModel unityMqModel, JudgementUnityModel unityModel){
Map<String, String> map = fillExtInfo(unityModel);
unityMqModel.setExtInfo(map);
}
mapstruct
生成的方法后,fillExtInfo
会放在方法的最后。
说明:
@MappingTarget
用来指定需要更新的对象。或者说,假设我们不想new一个对象,就可以使用@MappingTarget
进行指定。@AfterMapping
作用于生成方法的尾部,并且作用于相同映射关系的全部方法。
具有多个源参数的映射方法
@Mapper
public interface AddressMapper {
@Mapping(target = "description", source = "person.description")
@Mapping(target = "houseNumber", source = "address.houseNo")
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}
入参为null的情况下,希望返回空对象
例如:
List<JudgementModel> resToModelList(List<LiabilityResponse> res);
默认情况下,入参:res为null时,返回的也是null。
这种情况下,可以添加:
//策略
@IterableMapping(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT)
List<JudgementModel> resToModelList(List<LiabilityResponse> res);
总共有:@BeanMapping、@IterableMapping、@MapMapping 或全局@Mapper 或@MapperConfig
,可以更改映射结果以返回空默认值。这意味着:
官方说明:https://mapstruct.org/documentation/stable/reference/html/#mapping-result-for-null-arguments
没生效的情况
对象模型继承父类字段
父类字段没有生成 setXXX代码。
多半是因为lombok
和mapstruct
先后顺序导致的:
<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>
<!-- 注意顺序,lombok在前,mapstruct在后 -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
总结
目前常用的:
@Mapping
对字段一一指定- 使用
express
指定转换方法 - 使用
@afterMapping
是放到方法的尾部。当然也有@beforeMapping
,它有点特点,如果没有@MappingTarget
,那么会在创建bean之前调用,如果有,就是创建bean之后调用。具体参考官方文档。
参考地址: