mapStruct对象复制工具在项目中使用以及避坑指南

一、背景

在代码开发中,我们通常都会使用分层架构,在分层架构中都会使用模型转换,在不同的层使用不同的模型。以 DDD 分层模型为例,如下:
在这里插入图片描述

常见的对象拷贝工具有:Apache BeanUtils、Spring BeanUtils、cglib BeanCopier、HuTool BeanUtils、MapStruct、getter & setter

下方表格是对以上对象拷贝工具的对比,以供大家在选择使用拷贝工具时作参考

拷贝工具性能使用场景人力成本
Apache BeanUtils基于反射实现,性能较差简单的对象拷贝参数顺序和其它的工具正好相反,导致使用不顺手,容易产生问题,便捷
Spring BeanUtils基于内省+反射,借助getter/setter方法实现属性拷贝,性能比apache高简单的对象拷贝直接使用Spring 包下的BeanUtils.copyProperties()即可,便捷
cglib BeanCopier通过动态代理的方式来实现属性拷贝、性能高效简单的对象拷贝直接使用BeanCopier自带方法即可,便捷
HuTool BeanUtils性能介于apache和Spring之间简单的对象拷贝需要额外引入HuTool的依赖,不便捷
MapStruct基于getter/setter方法实现属性拷贝,在编译时自动生成实现类的代码,性能媲美getter & sette可直接进行集合转换、能够实现深度拷贝需要额外声明bean的转换接口,不太便捷
getter & setter性能最高简单的对象拷贝需手动拷贝,不便捷

以上是几种拷贝工具的对比,个人比较建议:简单对象拷贝使用BeanCopier,复杂对象封装(如集合转换,两个实体合为一个实体,vo和dto字段不一致等)可以考虑使用MapStruct对象封装工具。

二、项目使用案例

1.依赖引入

<dependency>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct</artifactId>
                <version>1.5.3.Final</version>
            </dependency>
 <dependency>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.5.3.Final</version>
 </dependency>

2.项目使用

需求:

1.将ConfigStationDTO对象转换为StationVO对象(ConfigStationDTO的id字段需对应StationVO的sid字段)

2.将ConfigStationDTOList集合转换为StationVOList集合

3.将ConfigStationDTO和ConfigSceneDTO对象合并转换为StationVO对象

代码:
1.具体实现

首先需要定义一个转换接口并继承封装好的CommonConvert接口(CommonConvert接口中已经定义了四个方法,分别是:vo转dto、dto转vo、dtoList转voList、voList转dtoList

public interface CommonConvert<D, V> {
    /**
     * DTO转VO
     */
    V toVO(D dto);

    /**
     * VO转DTO
     */
    D toDTO(V vo);

    /**
     * DTO集合转VO集合
     */
    List<V> toVOList(List<D> dtoList);

    /**
     * VO集合转DTO集合
     */
    List<D> toDTOList(List<V> voList);

}

定义具体的转换接口

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ConfigStationConvert extends CommonConvert<ConfigStationDTO, StationVO> {
    /**
     * 如有字段不一致,重写转换方法,使用@Mappings映射,如下
     */
    @Override
    @Mappings({
            @Mapping(source = "id", target = "sid")
    })
    StationVO toVO(ConfigStationDTO dto);

    /**
     * 如有两个实体合并到一个实体的需求,案例如下
     * 注意事项:如果入参中的两个实体都有转换后对象中的字段,必须指定使用哪个实体的字段
     */
    @Mappings({
            @Mapping(source = "stationDTO.uuid", target = "uuid"),
            @Mapping(source = "stationDTO.projectUuid", target = "projectUuid"),
            @Mapping(source = "configSceneDTO.name", target = "name"),
            @Mapping(source = "configSceneDTO.description", target = "description"),
            @Mapping(source = "configSceneDTO.createTime", target = "createTime")
    })
    StationVO toVO(ConfigStationDTO stationDTO, ConfigSceneDTO configSceneDTO);
}

备注:1.@Mapper()中componentModel = "spring"是用来将此接口交给spring管理,unmappedTargetPolicy = ReportingPolicy.IGNORE是用来忽略未匹配字段

​ 2.重写的toVO()方法能够满足第1个需求,@Mappings注解是用来做参数转换的,source是入参中的字段,target是出参中的字段

​ 3.CommonConvert中toVOList()方法可以满足第2个需求

​ 4.manytoVO()方法能够满足第3个需求

2.测试case
@SpringBootTest
public class TestMapStructDemo {
    
    // 因为我们定义接口时已经交由spring进行管理,所以此处可以直接使用@Autowired注入
    @Autowired
    private ConfigStationConvert configStationConvert;
    
    @Test
    void contextLoads() {
        System.out.println("123");
        ConfigStationDTO configStationDTO = new ConfigStationDTO();
        configStationDTO.setId(123L);
        configStationDTO.setUuid(1111111111111111L);
        configStationDTO.setName("站1");
        configStationDTO.setDescription("站1");
        StationVO stationVO1 = configStationConvert.toVO(configStationDTO);
        System.out.println("第一个需求字段不一致转换结果 = " + stationVO1);

        ConfigStationDTO configStationDTO2 = new ConfigStationDTO();
        configStationDTO2.setUuid(222222222222222222L);
        configStationDTO2.setName("站2");
        configStationDTO2.setDescription("站2");
        ConfigStationDTO configStationDTO3 = new ConfigStationDTO();
        configStationDTO3.setUuid(333333333333333333L);
        configStationDTO3.setName("站3");
        configStationDTO3.setDescription("站3");
        ArrayList<ConfigStationDTO> configStationDTOS = Lists.newArrayList(configStationDTO2, configStationDTO3);
        List<StationVO> stationVOS = configStationConvert.toVOList(configStationDTOS);
        System.out.println("第二个需求list转换结果 = " + stationVOS);

        ConfigSceneDTO configSceneDTO = new ConfigSceneDTO();
        configSceneDTO.setName("场景名");
        StationVO stationVO = configStationConvert.manytoVO(configStationDTO, configSceneDTO);
        System.out.println("第三个需求多对象合并结果 = " + stationVO);

    }
}

3.测试结果
第一个需求字段不一致转换结果 = StationVO(sid=123, uuid=1111111111111111, projectUuid=null, name=站1, description=站1, destinationCode=null, createTime=null, updateTime=null, customAttributes=null)
第二个需求list转换结果 = [StationVO(sid=null, uuid=222222222222222222, projectUuid=null, name=站2, description=站2, destinationCode=null, createTime=null, updateTime=null, customAttributes=null), StationVO(sid=null, uuid=333333333333333333, projectUuid=null, name=站3, description=站3, destinationCode=null, createTime=null, updateTime=null, customAttributes=null)]
第三个需求多对象合并结果 = StationVO(sid=null, uuid=1111111111111111, projectUuid=null, name=场景名, description=null, destinationCode=null, createTime=null, updateTime=null, customAttributes=null)

三、注意事项

使用过程中踩了两个坑,贴在这里以供大家借鉴

1.自定义转换接口存放的文件夹命名一定不能是mapper,因为mybatis-plus的mapper-scan会扫描mapper文件夹,此时@Mapper注解就会和mybatis-plus冲突

2.如果使用多对象合并为一个对象时,入参中的两个实体都有将转换的对象中的字段,必须指定使用哪个实体的字段。

​ 如:ConfigStationDTO中有uuid字段,ConfigSceneDTO中也有uuid字段,此时不指定@Mapping(source = “stationDTO.uuid”, target = “uuid”),,编译器编译时就会报无法匹配的错误

3.如果你项目中也使用了 Lombok,需要注意一下 Lombok 的版本至少是 1.18.10 或者以上才行,否则会出现编译失败的情况。

四、小结

以上就是MapStruct工具类的一些使用心得和踩坑总结。

附:
Idea插件:搜索MapStruct Support安装即可,可以在使用MapStruct时获得更加丰富代码提示。
官网地址:mapStruct官网地址

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值