美文网首页
MapStruct使用

MapStruct使用

作者: 西瓜雪梨桔子汁 | 来源:发表于2021-01-24 21:19 被阅读0次

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<String> 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<String> 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依赖

       <dependencies>
           <!-- …… -->

            <dependency>
                <!-- jdk8以下就使用mapstruct -->
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-jdk8</artifactId>
                <version>${org.mapstruct.version}</version>
            </dependency>
            <dependency>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>${org.mapstruct.version}</version>
            </dependency>

       </dependencies>


        <plugins>
           <!-- …… -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <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>${lombook.version}</version>
                        </path>
                    </annotationProcessorPaths>
                    <compilerArgs>
                        <compilerArg>
                            -Amapstruct.suppressGeneratorTimestamp=true
                        </compilerArg>
                        <compilerArg>
                            -Amapstruct.suppressGeneratorVersionInfoComment=true
                        </compilerArg>
                    </compilerArgs>
                </configuration>
            </plugin>
        </plugins>

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<String> list = request.getAttrs();
        if ( list != null ) {
            trackingDataConfigVo.setAttrs( new ArrayList<String>( 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<String> 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<String>();
    }

注意需要在接口配置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")

相关文章

  • MapStruct使用

    背景 在一个成熟可维护的工程中,细分模块后,domian工程最好不要被其他工程依赖,但是实体类一般存于domain...

  • MapStruct 使用

    对象映射工具的由来 大型项目采用分层开发,每层的数据模型都不同:在持久化层,模型层为 PO(Persistent ...

  • mapstruct使用

    一、maven依赖 二、plugin插件 注意:lombok插件必须同时配置,如果你使用了lombok插件的话 三...

  • mapStruct使用

    https://mapstruct.org/

  • MapStruct使用

    1.对象属性映射的苦恼 在日常开发中,常常涉及到接收Request对象,属性映射到内部交互的VO对象、甚至需要进一...

  • mapstruct使用

    增驾依赖

  • mapstruct使用

    参考

  • MapStruct实现对象映射

    1 序 MapStruct是一个属性映射工具,只需要使用@Mapper注解标注的映射接口。MapStruct就会自...

  • mapstruct 和lombok 结合之后mapstruct生

    lombok和mapstruct配合转换bean后,mapstruct生成空的实现. 如果出现mapstruct和...

  • Mapstruct-使用

    前言:最近在项目中看到有同事使用这样的注解,之前没有见到过,立即百度了下,噢,原来是一个属性映射工具,只需要定义一...

网友评论

      本文标题:MapStruct使用

      本文链接:https://www.haomeiwen.com/subject/rzxvzktx.html