struct用法 java,MapStruct使用

1.对象属性映射的苦恼

在日常开发中,常常涉及到接收Request对象,属性映射到内部交互的VO对象、甚至需要进一步映射到DTO对象,以完成相关的业务逻辑。

举个最近的栗子,接收的业务请求对象是这样:

@Data

@ApiModel(description = "配置信息请求体")

public class TrackingDataConfigRequest {

@NotBlank(message = "dimension不能为空,可选值:PROJECT、TENANT、BRAND、VEHICLE_SERIE、VEHICLE_MODEL、EVENT、VIN")

@ApiModelProperty(value = "dimension", required = true)

String dimension;

@ApiModelProperty(value = "brand", required = false)

String brand;

@ApiModelProperty(value = "vehicleSeries", required = false)

String vehicleSeries;

@ApiModelProperty(value = "vehicleModel", required = false)

String vehicleModel;

@ApiModelProperty(value = "eventId", required = false)

String eventId;

@ApiModelProperty(value = "vin", required = false)

String vin;

@ApiModelProperty(value = "attrs", required = false)

List attrs;

@ApiModelProperty(value = "switch", required = false)

String switchState;

@ApiModelProperty(value = "reportFrequency", required = false)

Integer reportFrequency;

@ApiModelProperty(value = "reportRecords", required = false)

Integer reportRecords;

@ApiModelProperty(value = "reportSize", required = false)

Integer reportSize;

}

请求体的非@NotBlank注解的字段为非必填(可能有值、也可以没有)。接口接收到请求参数后,需要转为如下内部交互VO结构:

public class TrackingDataConfigVo {

String projectId;

String tenantId;

String dimension;

String brand;

String vehicleSeries;

String vehicleModel;

String eventId;

String vin;

List attrs;

String switchState;

Integer reportFrequency;

Integer reportRecords;

Integer reportSize;

}

要实现这个映射有很多办法,比如:

最直接地,一个个请求体对象判断有值、设置到VO对象。更友好点, 针对VO对象提供Builder,实现链式属性赋值操作、最后build出对象

使用BeanUtils这样的工具类,比如org.springframework.beans.BeanUtils,还有Apache的org.apache.commons.beanutils.BeanUtilsBean。

痛点就不必赘述了,BeanUtils工具类的主要问题是只能针对映射对象、被映射对象同名同类型属性进行映射,但是实际开发场景还有很多属性名称不同、类型不同、属性默认值、属性需要按规则生成的场景,导致BeanUtils工具类失效。

2.MapStruct实现对象属性映射

就以文章开头提到的两个对象映射为例,大致需要如下操作。

2.1引入pom依赖

org.mapstruct

mapstruct-jdk8

${org.mapstruct.version}

org.mapstruct

mapstruct-processor

${org.mapstruct.version}

org.apache.maven.plugins

maven-compiler-plugin

1.8

1.8

org.mapstruct

mapstruct-processor

${org.mapstruct.version}

org.projectlombok

lombok

${lombook.version}

-Amapstruct.suppressGeneratorTimestamp=true

-Amapstruct.suppressGeneratorVersionInfoComment=true

mapstruct版本项目中使用的1.3.1.Final,最新的版本查了下应该是1.4.1.Final,没仔细看区别。

2.2 建立属性映射关系

需要新建一个接口,指明映射对象与被映射对象,如果有字段需要特殊的映射规则,可以在转换方法配置,比如目标映射属性与源属性名称不同、属性类型不同等等。

@Mapper(componentModel = "spring")

public interface GeneralBurrypointConfigConvertor {

/**

* 完成请求TrackingDataConfigRequest到TrackingDataConfigVo属性映射(projectId等参数无法映射)

*

* @param request

* @return

*/

@Mappings({

@Mapping(target = "projectId", defaultValue = ""),

@Mapping(target = "tenantId", defaultValue = "")})

TrackingDataConfigVo convertVo(TrackingDataConfigRequest request);

}

简单说明下:

componentModel = "spring"是因为使用Spring构建的项目,这里配置是指该接口生成的实现类上面会自动添加一个@Component注解,可以通过Spring的@Autowired方式进行注入.

由于请求的TrackingDataConfigRequest没有projectId、tenantId参数,所以映射的规则增加了这两个属性的默认值配置。

其它参数类型、属性都是一一对应,所以“约定大于配置”,就默认逐个属性都会映射。

2.3 编译生成实现类

这里补充说明下,mapstruct是在编译时期生成这个接口的实现类,所以不用有反射、性能这样的担忧。

生成的实现类如下:

@Generated(

value = "org.mapstruct.ap.MappingProcessor"

)

@Component

public class GeneralBurrypointConfigConvertorImpl implements GeneralBurrypointConfigConvertor {

@Override

public TrackingDataConfigVo convertVo(TrackingDataConfigRequest request) {

if ( request == null ) {

return null;

}

TrackingDataConfigVo trackingDataConfigVo = new TrackingDataConfigVo();

trackingDataConfigVo.setDimension( request.getDimension() );

trackingDataConfigVo.setBrand( request.getBrand() );

trackingDataConfigVo.setVehicleSeries( request.getVehicleSeries() );

trackingDataConfigVo.setVehicleModel( request.getVehicleModel() );

trackingDataConfigVo.setEventId( request.getEventId() );

trackingDataConfigVo.setVin( request.getVin() );

List list = request.getAttrs();

if ( list != null ) {

trackingDataConfigVo.setAttrs( new ArrayList( list ) );

}

trackingDataConfigVo.setSwitchState( request.getSwitchState() );

trackingDataConfigVo.setReportFrequency( request.getReportFrequency() );

trackingDataConfigVo.setReportRecords( request.getReportRecords() );

trackingDataConfigVo.setReportSize( request.getReportSize() );

return trackingDataConfigVo;

}

}

3. 其它场景展示

3.1 不同属性类型之间映射

比如源属性为String,由逗号分隔属性组成,类似:“a,b, c……”,目标对象属性为List类型。解决办法是实现一个工具类方法完成String转List,配置mapstruct规则即可:

@Mapper(componentModel = "spring",

imports = {ConfigConvertUtil.class},

unmappedTargetPolicy = ReportingPolicy.IGNORE)

public interface GeneralBurrypointConfigConvertor {

@Mappings({

@Mapping(target = "attrs", expression = "java( ConfigConvertUtil.convertStringToList( config.getAttrs() ) )")})

TrackingDataConfigVo convertVo(TdpGeneralBurrypointConfig config);

}

转换方法如下:

public static List convertStringToList(String attrs) {

if (!StringUtils.isEmpty(attrs)) {

return Arrays.asList(attrs.split(CommonConstants.COMMN_SIGN))

.stream().map(s -> (s.trim()))

.collect(Collectors.toList());

}

return new ArrayList();

}

注意需要在接口配置ConfigConvertUtil工具方法。

可以顺便看到实现类:

@Override

public TrackingDataConfigVo convertVo(TdpGeneralBurrypointConfig config) {

if ( config == null ) {

return null;

}

TrackingDataConfigVo trackingDataConfigVo = new TrackingDataConfigVo();

trackingDataConfigVo.setProjectId( config.getProjectId() );

trackingDataConfigVo.setTenantId( config.getTenantId() );

trackingDataConfigVo.setDimension( config.getDimension() );

trackingDataConfigVo.setBrand( config.getBrand() );

trackingDataConfigVo.setVehicleSeries( config.getVehicleSeries() );

trackingDataConfigVo.setVehicleModel( config.getVehicleModel() );

trackingDataConfigVo.setEventId( config.getEventId() );

trackingDataConfigVo.setVin( config.getVin() );

trackingDataConfigVo.setSwitchState( config.getSwitchState() );

trackingDataConfigVo.setReportFrequency( config.getReportFrequency() );

trackingDataConfigVo.setReportRecords( config.getReportRecords() );

trackingDataConfigVo.setReportSize( config.getReportSize() );

// 实现类使用了配置指定的工具类进行属性值处理

trackingDataConfigVo.setAttrs( ConfigConvertUtil.convertStringToList( config.getAttrs() ) );

return trackingDataConfigVo;

}

3.2 属性默认值生成

@Mapper(componentModel = "spring",

imports = {ConfigConvertUtil.class, DeleteFlagType.class, CommonConstants.class, Date.class},

unmappedTargetPolicy = ReportingPolicy.IGNORE)

public interface GeneralBurrypointConfigConvertor {

GeneralBurrypointConfigConvertor INSTANCE = Mappers.getMapper(GeneralBurrypointConfigConvertor.class);

/**

* TrackingDataConfigVo转为数据库TdpGeneralBurrypointConfig,需要增加无法映射的一些默认值

*

* @param config

* @return

*/

@Mappings({

@Mapping(target = "configId", expression = "java( ConfigConvertUtil.generateConfigId( config ) )"),

@Mapping(target = "deleteFlag", expression = "java( DeleteFlagType.VALIDATE.getCode() )"),

@Mapping(target = "attrs", expression = "java( ConfigConvertUtil.convertListToString( config.getAttrs() ) )"),

@Mapping(target = "createBy", expression = "java( CommonConstants.DEFAULT_ADMINASTRATOR )"),

@Mapping(target = "updateBy", expression = "java( CommonConstants.DEFAULT_ADMINASTRATOR )"),

@Mapping(target = "createTime", expression = "java( new Date() )"),

@Mapping(target = "updateTime", expression = "java( new Date() )")})

TdpGeneralBurrypointConfig convertDto(TrackingDataConfigVo config);

// ……

}

注意这些属性的映射:

configId:使用工具类,对请求体提取固定参数值按规则生成,expression表示按照此规则生成。

deleteFlag:是利用了枚举类的赋值对象的默认值。

createBy、updateBy:使用的常量类的值赋值对象的默认值。

createTime、updateTime:使用的是Date实现设置赋值对象属性当前时间。

检查实现类可以进一步确认:

@Override

public TdpGeneralBurrypointConfig convertDto(TrackingDataConfigVo config) {

if ( config == null ) {

return null;

}

TdpGeneralBurrypointConfig tdpGeneralBurrypointConfig = new TdpGeneralBurrypointConfig();

tdpGeneralBurrypointConfig.setProjectId( config.getProjectId() );

tdpGeneralBurrypointConfig.setTenantId( config.getTenantId() );

tdpGeneralBurrypointConfig.setDimension( config.getDimension() );

tdpGeneralBurrypointConfig.setBrand( config.getBrand() );

tdpGeneralBurrypointConfig.setVehicleSeries( config.getVehicleSeries() );

tdpGeneralBurrypointConfig.setVehicleModel( config.getVehicleModel() );

tdpGeneralBurrypointConfig.setEventId( config.getEventId() );

tdpGeneralBurrypointConfig.setVin( config.getVin() );

tdpGeneralBurrypointConfig.setSwitchState( config.getSwitchState() );

tdpGeneralBurrypointConfig.setReportFrequency( config.getReportFrequency() );

tdpGeneralBurrypointConfig.setReportRecords( config.getReportRecords() );

tdpGeneralBurrypointConfig.setReportSize( config.getReportSize() );

// 配置的默认值生成规则

tdpGeneralBurrypointConfig.setDeleteFlag( DeleteFlagType.VALIDATE.getCode() );

tdpGeneralBurrypointConfig.setCreateBy( CommonConstants.DEFAULT_ADMINASTRATOR );

tdpGeneralBurrypointConfig.setUpdateBy( CommonConstants.DEFAULT_ADMINASTRATOR );

tdpGeneralBurrypointConfig.setCreateTime( new Date() );

tdpGeneralBurrypointConfig.setConfigId( ConfigConvertUtil.generateConfigId( config ) );

tdpGeneralBurrypointConfig.setUpdateTime( new Date() );

tdpGeneralBurrypointConfig.setAttrs( ConfigConvertUtil.convertListToString( config.getAttrs() ) );

return tdpGeneralBurrypointConfig;

}

4. 遇到的问题

在使用过程中,由于指定了MyBatis的扫描范围为整个项目的包,导致误把MapStruct的@Mapper注解也纳入扫描范围,找不到数据库操作实现。所以,教训是配置MyBatis的扫描包范围时,一定避开MapStruct的包范围,比如指定最小化到dao层:@MapperScan("com.fawvw.ms.tdp.data.config.dao")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值