还用 BeanUtils 拷贝对象?MapStruct 才是王者!一文玩转 MapStruct 全场景【附源码】

  |   0 评论   |   浏览

大家好,我是mbb

作为一名基于Spring摸爬滚打了数年的码农;各种无脑的苦力活,可以说至少占据了一半的变成人生;比如说,对象拷贝,无脑的get、set调用;但是基于MVC下,各种实体间的转换,又是必不可少的。

你平常都用什么方式来做对象拷贝呢?

BeanUtils

因为是 Spring 自带的拷贝功能,所以出境率比较的高;但是在实际使用 BeanUtils 过程中,你是否遇到以下的一些小问题:

  • 属性类型不一样,无法进行拷贝,如数据库中查出来的Date,想转换成时间戳返回给前端;不好意思!不行!另外处理;
  • 只想拷贝部分字段,但是没办法忽略;对不起,不管三七二十一,一顿拷贝;完了再特殊处理;
  • 无法对属性进行规则转换;比如数据库中查询出来的0和1想在转换成VO之后变成true和false;sorry!不支持,自行搞定;
  • 性能低

虽然基础的拷贝功能可以做到,但是总觉得跟个糙汉子一样;很多细节都没有做处理,只能单独再做二次加工;

MapStruct

既然 BeanUtils 各种别扭,那有没有更好的方式可以解决这些问题呢?

当然是有的;

那就是今天要详细介绍的对象拷贝的王者:MapStruct

上面说的这些问题,通通都能解决了;

上面把 BeanUtils 比作糙汉子,那 MapStruct 就可以称之为大家闺秀,心细如发,开发过程中能遇到的问题,他都给出了解决方案,完美帮你解决。

MapStruct

什么是 MapStruct?

MapStruct 是一个代码生成器,它基于约定优于配置方法,极大地简化了 Java bean 类型之间映射的实现。

生成的映射代码使用简单的方法调用,因此速度快、类型安全且易于理解。 ---- 来源于官网

性能

以下是Java各种拷贝方式的耗时对比:

MapStruct的优点

相比于手动get、set

无需手写转换工具类,减轻大量的体力活

相比与其他动态映射

  • 效率高

    核心的转换逻辑并不是通过反射实现,而是通过编译时自动生成基于 getter/setter 转换实现类;

  • 性能高

    基于简单的get、set操作,效率达到最佳

  • 编译时类型安全

    只能映射相同名称或带映射标记的属性;

  • 编译时产生错误报告

    如果映射不完整或映射不正确则会在编译时抛出异常,代码将无法正常运行;

  • 能明确查看转换的细节

    编译生成的class对象可以看到详细的转换过程,方便快速定位转换过程中的问题。

MapStruct 常用的重要注解 :

  • @Mapper

    标记这个接口作为一个映射接口,并且是编译时 MapStruct 处理器的入口

  • @Mapping

    解决源对象和目标对象中,属性名字不同的情况

  • @Mappings

    当存在多个 @Mapping 需要配置;可以通过 @Mappings 批量指定

  • Mappers.getMapper

    Mapper 的 class 获取自动生成的实现对象,从而让客户端可以访问 Mapper 接口的实现

使用

测试代码

https://github.com/mbb2100/mapstruct-demo

准备

  • 依赖

    最新的版本可以通过下面的链接查看

    https://mvnrepository.com/artifact/org.MapStruct/MapStruct-jdk8

    https://mvnrepository.com/artifact/org.MapStruct/MapStruct-processor

    <properties>
         <MapStruct.version>1.3.1.Final</MapStruct.version>
    </properties>
    
    <!-- https://mvnrepository.com/artifact/org.MapStruct/MapStruct-jdk8 -->
    <dependency>
         <groupId>org.MapStruct</groupId>
         <artifactId>MapStruct-jdk8</artifactId>
         <version>${MapStruct.version}</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.MapStruct/MapStruct-processor -->
    <dependency>
         <groupId>org.MapStruct</groupId>
         <artifactId>MapStructrocessor</artifactId>
         <version>${MapStruct.version}</version>
    </dependency>
    
    <!-- 非必须 注意:版本过高可能造成对象无法生成-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.22</version>
    </dependency>
    
  • 基础测试对象

    @Data
    @Builder
    @ToString
    public class UserDTO {
        private String name;
    
        private Integer age;
    
        private Date createTime;
    }
    
    @Data
    @ToString
    public class UserVO {
        private String name;
    
        private Integer age;
    
        private Date createTime;
    }
    
    @Data
    @ToString
    public class UserVO1 {
        private String name;
    
        private Integer age;
    
    	// 类型和VO对象不同
        private String createTime;
    }
    

BeanUtils拷贝演示

简单的演示一下BeanUtils拷贝

public class t1 {
    public static void main(String[] args) {
        UserDTO userDTO = UserDTO.builder()
                .name("张三")
                .age(10)
                .createTime(new Date())
                .build();

        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(userDTO,userVO);
        System.out.println(userVO);

        UserVO1 userVO1 = new UserVO1();
        BeanUtils.copyProperties(userDTO,userVO1);
        System.out.println(userVO1);
    }
}

可以看到,文章一开始说的问题,就慢慢在暴露了

MapStruct基本功能演示

  • 第一步,定义Mapper

    // spring方式加载
    @Mapper(componentModel = "spring")
    public interface UserMapper {
        // default方式加载
        UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    
        /**
         * 将DTO转VO
         *
         * @param userDTO
         * @return
         */
        UserVO userVO2UserDTO(UserDTO userDTO);
    }
    

    componentModel 属性用于指定自动生成的接口实现类的组件类型,这个属性支持四个值:

    • default: 这是默认的情况;通过ClassLoader加载
    • spring: 生成的实现类上面会自动添加一个@Component注解,可以通过Spring的 @Autowired方式进行注入
    • jsr330: 生成的实现类上会添加@javax.inject.Named 和@Singleton注解,可以通过 @Inject注解获取
    • cdi: 生成的映射器是一个应用程序范围的CDI bean,可以通过检索 @Inject
  • 第二步,测试default和spring方式

    • default

      UserDTO userDTO = UserDTO.builder()
              .name("张三")
              .age(10)
              .createTime(new Date())
              .build();
      UserVO userVO = UserMapper.INSTANCE.userVO2UserDTO(userDTO);
      System.out.println(userVO);
      

      自动生成的UserMapperImpl.class

      public class UserMapperImpl implements UserMapper {
          public UserMapperImpl() {
          }
      
          public UserVO userVO2UserDTO(UserDTO userDTO) {
              if (userDTO == null) {
                  return null;
              } else {
                  UserVO userVO = new UserVO();
                  userVO.setName(userDTO.getName());
                  userVO.setAge(userDTO.getAge());
                  userVO.setCreateTime(userDTO.getCreateTime());
                  return userVO;
              }
          }
      }
      

    • spring方式

      @Autowired
      UserMapper userMapper;
      
      @Test
      void springTest() {
          UserDTO userDTO = UserDTO.builder()
                  .name("张三")
                  .age(10)
                  .createTime(new Date())
                  .build();
          UserVO userVO = userMapper.userVO2UserDTO(userDTO);
          System.out.println(userVO);
      }
      

      自动生成的UserMapperImpl.class

      @Component
      public class UserMapperImpl implements UserMapper {
          public UserMapperImpl() {
          }
      
          public UserVO userVO2UserDTO(UserDTO userDTO) {
              if (userDTO == null) {
                  return null;
              } else {
                  UserVO userVO = new UserVO();
                  userVO.setName(userDTO.getName());
                  userVO.setAge(userDTO.getAge());
                  userVO.setCreateTime(userDTO.getCreateTime());
                  return userVO;
              }
          }
      }
      

      可以看出实现类上面自动加上了 @Component,就可以通过 Spring 的方式注入对象并使用;

进一步封装

上面简单测试可以发现,需要做两个对象的转换,就得定义一个接口和数个互转的方法;

为了不用每次都去写那些重复的转换方法,这里对转换接口再向上做一次抽象;

  • 定义基础的转换接口

    包含了最基本的4种转换方式

    /**
     * 基础的对象转换Mapper
     *
     * @param <SOURCE> 源对象
     * @param <TARGET> 目标对象
     */
    public interface BaseMapper<SOURCE, TARGET> {
        TARGET to(SOURCE var1);
    
        List<TARGET> to(List<SOURCE> var1);
    
    
        SOURCE from(TARGET var1);
    
        List<SOURCE> from(List<TARGET> var1);
    }
    
  • 修改UserMapper

    @Mapper(componentModel = "spring")
    public interface UserMapper extends BaseMapper<UserDTO, UserVO> {
        UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    }
    
  • 测试

    使用方式没有产生任何变化

    // default
    UserVO userVO = UserMapper.INSTANCE.to(userDTO);
    
    // spring
    UserVO userVO = userMapper.to(userDTO);
    

特殊场景

基础的场景已经会用了,但是往往实际的开发中,会面临各种各样奇奇怪怪的转换,这里就详细的列举一下各种特殊的情况。

日期转换

比如数据库中的日期对象 Date 需要转换成 yyyyMMdd 这样格式的对象

  • 测试对象

    @Data
    @ToString
    public class UserVO1 {
        private String name;
    
        private Integer age;
    
    	// 类型和VO对象不同
        private String createTime;
    }
    
  • Mapper定义

    @Mapper
    public interface User1Mapper extends BaseMapper<UserDTO, UserVO1>{
        User1Mapper INSTANCE = Mappers.getMapper(User1Mapper.class);
    
        @Mappings({
                @Mapping(source = "createTime",target = "createTime",dateFormat = "yyyyMMdd")
        })
        @Override
        UserVO1 to(UserDTO var1);
    }
    
  • 测试

    public class t2 {
        public static void main(String[] args) {
            UserDTO userDTO = UserDTO.builder()
                    .name("张三")
                    .age(10)
                    .createTime(new Date())
                    .build();
    
            UserVO1 userVO1 = User1Mapper.INSTANCE.to(userDTO);
            System.out.println(userVO1);
    
            List<UserDTO> userDTOS = new ArrayList<>();
            userDTOS.add(userDTO);
            List<UserVO1> userVO1s = User1Mapper.INSTANCE.to(userDTOS);
            System.out.println(userVO1s);
        }
    }
    

忽略指定字段

部分字段不进行拷贝操作;忽略主要是在Mapper的地方进行配置;

  • 测试对象

    采用 UserDTO 和 UserVO1 进行测试

  • Mapper

    @Mapper
    public interface User4Mapper extends BaseMapper<UserDTO, UserVO1>{
        User4Mapper INSTANCE = Mappers.getMapper(User4Mapper.class);
    
        @Mappings({
                // 要忽略的字段
                @Mapping(target = "createTime",ignore = true)
        })
        @Override
        UserVO1 to(UserDTO var1);
    }
    
  • 测试

    public class t4 {
        public static void main(String[] args) {
            UserDTO userDTO = UserDTO.builder()
                    .name("张三")
                    .age(10)
                    .createTime(new Date())
                    .build();
    
            UserVO1 userVO1 = User4Mapper.INSTANCE.to(userDTO);
            System.out.println(userVO1);
        }
    }
    

多数据源拷贝

多个数据源对象的数据拷贝到一个对象中

  • 测试对象

    // UserDTO 略...
    
    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    @ToString
    public class AddressDTO {
        private String country;
    
        private String province;
    
        private String city;
    }
    
  • Mapper

    @Mapper(componentModel = "spring")
    public interface User2Mapper {
        User2Mapper INSTANCE = Mappers.getMapper(User2Mapper.class);
    
        // 如果无特殊字段,可以不配置Mappings
        // 会自动把两个源对象中的属性复制到咪表对象
        @Mappings({
                @Mapping(source = "userDTO.name",target = "name"),
                @Mapping(source = "addressDTO.country",target = "country")
        })
        UserVO2 to(UserDTO userDTO, AddressDTO addressDTO);
    }
    
  • 测试

    public class t3 {
        public static void main(String[] args) {
            UserDTO userDTO = UserDTO.builder()
                    .name("张三")
                    .age(10)
                    .createTime(new Date())
                    .build();
    
            AddressDTO addressDTO = AddressDTO.builder()
                    .country("中国")
                    .province("北京")
                    .city("北京")
                    .build();
            UserVO2 userVO2 = User2Mapper.INSTANCE.to(userDTO, addressDTO);
            System.out.println(userVO2);
        }
    }
    

不同属性名之间的转换

两个对象间不同名称间属性值拷贝

  • 测试对象

    // UserDTO 略...
    
    @Data
    @ToString
    public class UserVO3 {
        private String nickName;
    }
    
  • Mapper

    @Mapper(componentModel = "spring")
    public interface User3Mapper extends BaseMapper<UserDTO,UserVO3>{
        User3Mapper INSTANCE = Mappers.getMapper(User3Mapper.class);
    
        @Mapping(source = "name", target = "nickName")
        @Override
        UserVO3 to(UserDTO var1);
    }
    
  • 测试

    public class t3 {
        public static void main(String[] args) {
            UserDTO userDTO = UserDTO.builder()
                    .name("张三")
                    .age(10)
                    .createTime(new Date())
                    .build();
    
            UserVO3 userVO3 = User3Mapper.INSTANCE.to(userDTO);
            System.out.println(userVO3);
        }
    }
    

互相转换(反向转换)

如上示例,将的 UserDTO.name 转换为 UserVO3.nickName ;同时 UserVO3.nickName 也要能正常转换为 UserDTO.name,就可以通过@InheritInverseConfiguration 来实现

  • 转换Mapper

    @Mapper(componentModel = "spring")
    public interface User3Mapper extends BaseMapper<UserDTO, UserVO3> {
        User3Mapper INSTANCE = Mappers.getMapper(User3Mapper.class);
    
        @Mapping(source = "name", target = "nickName")
        @Override
        UserVO3 to(UserDTO var1);
    
        // name为 A==>B 的方法名
        @InheritInverseConfiguration(name = "to")
        @Override
        UserDTO from(UserVO3 var1);
    }
    
  • 测试

    /**
     * 不同属性名之间的映射
     */
    public class t3 {
        public static void main(String[] args) {
            UserDTO userDTO = UserDTO.builder()
                    .name("张三")
                    .age(10)
                    .createTime(new Date())
                    .build();
    
            UserVO3 userVO3 = User3Mapper.INSTANCE.to(userDTO);
            System.out.println(userVO3);
    
            UserDTO userDTO1 = User3Mapper.INSTANCE.from(userVO3);
            System.out.println(userDTO1);
        }
    }
    

自定义格式转换

批量将一种类型的数据转换为另一种格式的数据;这里测试将所有的Date数据全部转换为 yyyy-MM-dd 的文本

  • 测试对象

    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    @ToString
    public class UserDTO {
        private String name;
    
        private Integer age;
    
        private Date createTime;
    
        private Date updateTime;
    }
    
    @Data
    @ToString
    public class UserVO1 {
        private String name;
    
        private Integer age;
    
        // 类型和VO对象不同
        private String createTime;
    
        // 类型和VO对象不同
        private String updateTime;
    }
    
  • 自定义日期格式转换Mapper

    public class DateMapper {
        public String toString(Date date) {
            return date != null ? new SimpleDateFormat("yyyy-MM-dd").format(date) : null;
        }
    
        public Date toDate(String date) {
            try {
                return date != null ? new SimpleDateFormat("yyyy-MM-dd").parse(date) : null;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    
  • 定义对象转换Mapper

    /**
     * 自定义日期格式转换映射器
     * uses = DateMapper.class
     */
    @Mapper(uses = DateMapper.class)
    public interface User5Mapper extends BaseMapper<UserDTO, UserVO1> {
        User5Mapper INSTANCE = Mappers.getMapper(User5Mapper.class);
    }
    
  • 测试

    public class t5 {
        public static void main(String[] args) {
            UserDTO userDTO = UserDTO.builder()
                    .name("张三")
                    .age(10)
                    .createTime(new Date())
                    .updateTime(new Date())
                    .build();
    
            UserVO1 userVO1 = User5Mapper.INSTANCE.to(userDTO);
            System.out.println(userVO1);
        }
    }
    

多种不同自定义转换作用于不同属性

  • 测试场景

    对象中的 Date 字段转换成不同格式的时间文本,比如转换成 yyyy-MM-dd 和 yyyy/MM/dd 两种格式

  • 测试对象

    同上

  • 自定义时间转换器

    格式一

    @Named("dateMapper1")
    public class DateMapper1 {
        public String toString(Date date) {
            return date != null ? new SimpleDateFormat("yyyy-MM-dd").format(date) : null;
        }
    
        public Date toDate(String date) {
            try {
                return date != null ? new SimpleDateFormat("yyyy-MM-dd").parse(date) : null;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    

    测试二

    @Named("dateMapper2")
    public class DateMapper2 {
        public String toString(Date date) {
            return date != null ? new SimpleDateFormat("yyyy/MM/dd").format(date) : null;
        }
    
        public Date toDate(String date) {
            try {
                return date != null ? new SimpleDateFormat("yyyy/MM/dd").parse(date) : null;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    
  • 定义对象转换Mapper

    /**
     * 自定义不同日期格式转换映射器
     * uses = DateMapper.class
     */
    @Mapper(uses = {
            DateMapper1.class,
            DateMapper2.class
    })
    public interface User6Mapper extends BaseMapper<UserDTO, UserVO1> {
        User6Mapper INSTANCE = Mappers.getMapper(User6Mapper.class);
    
        @Mappings({
                @Mapping(source = "createTime", target = "createTime", qualifiedByName = {"dateMapper1"}),
                @Mapping(source = "updateTime", target = "updateTime", qualifiedByName = {"dateMapper2"})
        })
        @Override
        UserVO1 to(UserDTO var1);
    }
    
  • 测试

    public class t6 {
        public static void main(String[] args) {
            UserDTO userDTO = UserDTO.builder()
                    .name("张三")
                    .age(10)
                    .createTime(new Date())
                    .updateTime(new Date())
                    .build();
    
            UserVO1 userVO1 = User5Mapper.INSTANCE.to(userDTO);
            System.out.println(userVO1);
        }
    }
    

数字类型转换

  • 场景

    如果是基本的数据类型与文本之间的转换,默认情况下MapStruct 已经帮我们做好了,比如int与 string 的互转,就会自动通过String.valueof 以及 Integer.tostring 等方法进行转换了;

    但是还存在一些特殊场景;比如高精度转换低精度,需要取小数点后多少位等,就需要特殊处理;

    这里就来测试一个 double 转 string 保留两位小数的场景

  • 测试对象

    UserDTO添加以下字段

    private Double wallet;
    

    UserVO1添加以下字段

    private String wallet;
    
  • 定义对象转换Mapper

    /**
     * 数值类型格式化
     */
    @Mapper
    public interface User7Mapper extends BaseMapper<UserDTO, UserVO1> {
        User7Mapper INSTANCE = Mappers.getMapper(User7Mapper.class);
    
        @Mapping(source = "wallet", target = "wallet", numberFormat = "$#.00")
        @Override
        UserVO1 to(UserDTO var1);
    
        @Mapping(source = "wallet", target = "wallet", numberFormat = "$#.00")
        @Override
        UserDTO from(UserVO1 var1);
    
        @IterableMapping(numberFormat = "$#.00")
        List<String> doubleList2String(List<Double> vas);
    
        @IterableMapping(numberFormat = "$#.00")
        List<Double> stringList2Double(List<String> vas);
    }
    
  • 测试

    /**
     * 数值类型转换格式化
     */
    public class t7 {
        public static void main(String[] args) {
            UserDTO userDTO = UserDTO.builder()
                    .name("张三")
                    .age(10)
                    .createTime(new Date())
                    .updateTime(new Date())
                    .wallet(10000.45678)
                    .build();
    
            UserVO1 userVO1 = User7Mapper.INSTANCE.to(userDTO);
            System.out.println(userVO1);
    
            UserDTO userDTO1 = User7Mapper.INSTANCE.from(userVO1);
            System.out.println(userDTO1);
    
            List<Double> vas = new ArrayList<>();
            vas.add(123.5585);
            vas.add(784.1565488);
            vas.add(12.11243);
            // string list转 double
            List<String> strings = User7Mapper.INSTANCE.doubleList2String(vas);
            System.out.println(strings);
    
            // double list 转 string
            List<Double> doubles = User7Mapper.INSTANCE.stringList2Double(strings);
            System.out.println(doubles);
        }
    }
    

BigDecimal转换

  • 测试对象

    UserDTO添加以下属性

    private BigDecimal deposit;
    

    UserVO1添加以下属性

    private String deposit;
    
  • 定义对象转换Mapper

    /**
     * BigDecimal转换
     */
    @Mapper
    public interface User8Mapper extends BaseMapper<UserDTO, UserVO1> {
        User8Mapper INSTANCE = Mappers.getMapper(User8Mapper.class);
    
        @Mapping(source = "deposit", target = "deposit", numberFormat = "#.##E0")
        @Override
        UserVO1 to(UserDTO var1);
    
        @Mapping(source = "deposit", target = "deposit", numberFormat = "#.##E0")
        @Override
        UserDTO from(UserVO1 var1);
    }
    
  • 测试

    /**
     * BigDecimal转换测试
     */
    public class t8 {
        public static void main(String[] args) {
            UserDTO userDTO = UserDTO.builder()
                    .name("张三")
                    .age(10)
                    .createTime(new Date())
                    .updateTime(new Date())
                    .wallet(10000.45678)
                    .deposit(new BigDecimal(10000000.324))
                    .build();
    
            UserVO1 userVO1 = User8Mapper.INSTANCE.to(userDTO);
            System.out.println(userVO1);
    
            UserDTO userDTO1 = User8Mapper.INSTANCE.from(userVO1);
            System.out.println(userDTO1);
        }
    }
    

嵌套属性的转换

当对象中嵌套对象,且需要转换的时候,可以通过配置不同对象间的映射关系来完成嵌套映射

  • 测试对象

    UserDTO添加地址对象

    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    @ToString
    public class UserDTO {
        // 略...
    
        private AddressDTO addressDTO;
    }
    

    UserVO2

    @Data
    @ToString
    public class UserVO2 {
        private String name;
    
        private Integer age;
    
        private String country;
    
        private String province;
    
        private String city;
    }
    
  • 测试需求

    将UserDTO.addressDTO.country 属性映射到 UserVO2.country

  • 映射Mapper

    /**
     * 嵌套对象的映射
     */
    @Mapper
    public interface User9Mapper extends BaseMapper<UserDTO, UserVO2> {
        User9Mapper INSTANCE = Mappers.getMapper(User9Mapper.class);
    
        @Mapping(source = "addressDTO.country", target = "country")
        @Override
        UserVO2 to(UserDTO userDTO);
    
        // 反向配置
        @InheritInverseConfiguration(name = "to")
        @Override
        UserDTO from(UserVO2 var1);
    }
    
  • 测试代码

    /**
     * 嵌套对象的映射
     */
    public class t9 {
        public static void main(String[] args) {
            UserDTO userDTO = UserDTO.builder()
                    .name("张三")
                    .age(10)
                    .addressDTO(AddressDTO.builder().country("中国").build())
                    .build();
    
            UserVO2 userVO2 = User9Mapper.INSTANCE.to(userDTO);
            System.out.println(userVO2);
    
            UserDTO userDTO1 = User9Mapper.INSTANCE.from(userVO2);
            System.out.println(userDTO1);
        }
    }
    

BeanUtils与MapStruct性能对比

文章一开始就说到了 MapStruct 的性能要高于 BeanUtils ;经过了一轮使用之后,我们得来实测一下性能到底差多少?

  • 测试场景

    分别通过MapStruct 和 BeanUtils 将相同对象转换100W次,看看整体的耗时

  • 测试代码

    /**
     * BeanUtils与MapStruct性能对比
     */
    public class t10 {
        public static void main(String[] args) {
            for (int j = 0; j < 10; j++) {
                Long start = System.currentTimeMillis();
                for (int i = 0; i < 1000000; i++) {
                    UserDTO userDTO = UserDTO.builder()
                            .name("张三")
                            .age(10)
                            .build();
    
                    UserVO userVO = new UserVO();
                    BeanUtils.copyProperties(userDTO, userVO);
                }
                System.out.println("BeanUtils 100W次转换耗时:" + (System.currentTimeMillis() - start));
    
                start = System.currentTimeMillis();
                for (int i = 0; i < 1000000; i++) {
                    UserDTO userDTO = UserDTO.builder()
                            .name("张三")
                            .age(10)
                            .build();
    
                    UserVO1 userVO1 = User1Mapper.INSTANCE.to(userDTO);
                }
                System.out.println("MapStruct 100W次转换耗时:" + (System.currentTimeMillis() - start));
                System.out.println();
            }
        }
    }
    
  • 测试结果

    可以看出,相同的属性转换,发现性能确实不在一个数量级;

问题

问题一

找不到属性名

Error:(15, 5) java: No property named "xxx" exists in source parameter(s). Did you mean "null"?

lombok版本过高,将版本调低点

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.22</version>
</dependency>

问题二

修改无效

可以将target目录删除重新编译测试;防止因为修改为编译导致不生效的情况。

测试代码地址: https://github.com/mbb2100/mapstruct-demo



标题:还用 BeanUtils 拷贝对象?MapStruct 才是王者!一文玩转 MapStruct 全场景【附源码】
作者:码霸霸
地址:https://lupf.cn/articles/2021/06/11/1623373787754.html