MapStruct应用实战及BeanUtils性能比较

目录

    • 1、MapStruct介绍
    • 2、应用设置
      • 2.1 Maven依赖
    • 3、功能实战
      • 3.1 常用注解
      • 3.2 基本映射
        • 3.2.1 定义映射器的Java接口
        • 3.2.2 测试验证
      • 3.3 参数引用映射
        • 3.3.1 定义映射器的Java接口
        • 3.3.2 测试验证
      • 3.4 多对象参数映射
        • 3.4.1 定义映射器的Java接口
        • 3.4.2 测试验证
        • 3.4.3 注意点
      • 3.5 嵌套映射
        • 3.5.1 定义映射器的Java接口
        • 3.5.2 测试验证
        • 3.5.3 注意点
      • 3.6 对象更新
        • 3.6.1 定义映射器的Java接口
        • 3.6.2 测试验证
        • 3.6.3 注意点
    • 4、性能比较
      • 4.1 Apache BeanUtils 与 MapStruct比较
      • 4.2 Hutool BeanUtil与 MapStruct比较
      • 4.3 Spring BeanUtils 与 MapStruct比较
      • 4.4 Cglib BeanCopier 与 MapStruct比较
      • 4.4 性能对比结果(5次平均值)


1、MapStruct介绍

MapStruct官方文档

  1. MapStruct是一个Java注释处理器,用于生成类型安全的bean映射类,它基于约定优于配置方法,极大地简化了 Java bean
    类型之间映射的实现。
  2. 您所要做的就是定义一个mapper接口,该接口声明任何所需的映射方法。在编译期间,MapStruct将生成此接口的实现。此实现使用普通的Java方法调用来在源对象和目标对象之间进行映射,即没有反射或类似。
  3. 与手工编写映射代码相比,MapStruct通过生成繁琐且易于编写的代码来节省时间。遵循约定优于配置方法,MapStruct使用合理的默认值,但在配置或实现特殊行为时会采取措施。
  4. 与动态映射框架相比,MapStruct具有以下优势:
  • 通过使用普通方法调用而不是反射来快速执行

  • 编译时类型安全:只能映射相互映射的对象和属性,不会将订单实体意外映射到客户DTO等。

  • 在构建时清除错误报告,如果

    • 映射不完整(并非所有目标属性都已映射)
    • 映射不正确(找不到合适的映射方法或类型转换)

2、应用设置

2.1 Maven依赖


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>

    <groupId>com.siayougroupId>
    <artifactId>mapstruct-demoartifactId>
    <version>1.0-SNAPSHOTversion>

    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <maven.compiler.source>1.8maven.compiler.source>
        <maven.compiler.target>1.8maven.compiler.target>
        <org.mapstruct.version>1.4.1.Finalorg.mapstruct.version>
        <org.projectlombok.version>1.18.12org.projectlombok.version>
    properties>

    <dependencies>
        <dependency>
            <groupId>org.mapstructgroupId>
            <artifactId>mapstructartifactId>
            <version>${org.mapstruct.version}version>
        dependency>
        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>${org.projectlombok.version}version>
            <scope>providedscope>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>3.8.1version>
                <configuration>
                    <source>1.8source>
                    <target>1.8target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombokgroupId>
                            <artifactId>lombokartifactId>
                            <version>${org.projectlombok.version}version>
                        path>
                        <path>
                            <groupId>org.mapstructgroupId>
                            <artifactId>mapstruct-processorartifactId>
                            <version>${org.mapstruct.version}version>
                        path>
                    annotationProcessorPaths>
                configuration>
            plugin>
        plugins>
    build>
project>

3、功能实战

3.1 常用注解

  • @Mapper 标记这个接口作为一个映射接口,并且是编译时 MapStruct 处理器的入口
  • @Mapping 解决源对象和目标对象中,属性名字不同的情况
  • @Mappings 当存在多个 @Mapping 需要配置;可以通过 @Mappings 批量指定
  • Mappers.getMapper Mapper 的 class 获取自动生成的实现对象,从而让客户端可以访问 Mapper 接口的实现

3.2 基本映射

3.2.1 定义映射器的Java接口
@Mapper
public interface StudentMapper {
    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
    //mapper可以进行字段映射,改变字段类型,指定格式化的方式,包括一些日期的默认处理。
    //无论date转string,还是string转date,都是用dateFormat
    @Mapping(source = "gender.name", target = "gender")
    @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    StudentVO student2StudentVO(Student student);
}
// @Data 在编译时会自动添加 Getter、Setter、equals、canEqual、hasCode、toString 等方法,高效且代码非常简洁。
// @Builder 可代替需要的很多构造函数,解决了某个类有很多构造函数的情况。
// @AllArgsConstructor 在编译时会自动添加一个含有所有已声明字段的构造函数,不必再手动编写含有所有已声明字段的构造函数。
// @NoArgsConstructor 在编译时会自动添加一个无参的构造函数,不必再手动编写无参构造函数。
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    //姓名
    private String name;
    //年龄
    private int age;
    //性别
    private GenderEnum gender;
    //身高
    private Double height;
    //生日
    private Date birthday;
}

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StudentVO {

    //姓名
    private String name;
    //年龄
    private int age;
    //性别
    private String gender;
    //身高
    private Double height;
    //生日
    private String birthday;
}

public enum GenderEnum {
    Male("1", "男"),
    Female("0", "女");

    private String code;
    private String name;

    public String getCode() {
        return this.code;
    }

    public String getName() {
        return this.name;
    }

    GenderEnum(String code, String name) {
        this.code = code;
        this.name = name;
    }
}
3.2.2 测试验证
    public static void main(String[] args) {
        Student student = Student.builder()
                .name("张三")
                .age(16)
                .gender(GenderEnum.Male)
                .height(174.3)
                .birthday(new Date())
                .build();
        System.out.println(student);
        StudentVO studentVO = StudentMapper.INSTANCE.student2StudentVO(student);
        System.out.println(studentVO);
    }
    
 //测试结果
Student(name=张三, age=16, gender=Male, height=174.3, birthday=Fri Sep 22 16:22:38 CST 2023)
StudentVO(name=张三, age=16, gender=, height=174.3, birthday=2023-09-22 16:22:38)

3.3 参数引用映射

3.3.1 定义映射器的Java接口
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StudentVO {
    //姓名
    private String name;
    //年龄
    private int age;
    //性别
    private String gender;
    //身高
    private Double height;
    //生日
    private String birthday;
    private ExtendDto extendDto;
}

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    //姓名
    private String name;
    //年龄
    private int age;
    //性别
    private GenderEnum gender;
    //身高
    private Double height;
    //生日
    private Date birthday;

    private ExtendDto extendDto;
}

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ExtendDto {
    //邮箱
    private String email;
    //地址
    private String address;
    //电话
    private String phone;
}

@Mapper
public interface StudentMapper {
    @Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "email", target = "extendDto.email")
    StudentVO studentTwoStudentVO(Student student,String email);
}
3.3.2 测试验证
public static void main(String[] args) {
        Student student = Student.builder()
                .name("李四")
                .age(16)
                .gender(GenderEnum.Male)
                .height(174.3)
                .birthday(new Date())
                .build();
        System.out.println(student);
        StudentVO studentVO = StudentMapper.INSTANCE.studentTwoStudentVO(student,"[email protected]");
        System.out.println(studentVO);
    }
    
 //测试结果
Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:12:36 CST 2023, extendDto=null)
StudentVO(name=李四, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:12:36, extendDto=ExtendDto(email=dmjxsy@126.com, address=null, phone=null))

3.4 多对象参数映射

3.4.1 定义映射器的Java接口
    @Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "extendDto", target = "extendDto")
    StudentVO studentMapStudentVO(Student student,ExtendDto extendDto);
3.4.2 测试验证
    public static void main(String[] args) {
        Student student = Student.builder()
                .name("李四")
                .age(16)
                .gender(GenderEnum.Male)
                .height(174.3)
                .birthday(new Date())
                .build();
        ExtendDto extendDto = ExtendDto.builder()
                .email("[email protected]")
                .phone("119")
                .address("陕西")
                .build();
        System.out.println(student);
        System.out.println(extendDto);
        StudentVO studentVO = StudentMapper.INSTANCE.studentMapStudentVO(student,extendDto);
        System.out.println(studentVO);
}
 //测试结果
Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:24:18 CST 2023, extendDto=null)
ExtendDto(email=dmjxsy@126.com, address=陕西, phone=119)
StudentVO(name=李四, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:24:18, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=119))
    }
3.4.3 注意点

当配置了参数时,在配置对象属性关系映射时,也要显示的指明对象参数的名称。如@Mapping(source = “student.birthday”, target = “birthday”) 就显示指明了 source = “student.birthday” 。

3.5 嵌套映射

3.5.1 定义映射器的Java接口
    @Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "extendDto", target = ".")
    StudentVO studentMapStudentVO(Student student);
3.5.2 测试验证
    public static void main(String[] args) {
        Student student = Student.builder()
                .name("李四")
                .age(16)
                .gender(GenderEnum.Male)
                .height(174.3)
                .birthday(new Date())
                .extendDto( ExtendDto.builder()
                        .email("[email protected]")
                        .phone("110")
                        .address("陕西")
                        .build())
                .build();
        System.out.println(student);
        StudentVO studentVO = StudentMapper.INSTANCE.studentMapStudentVO(student);
        System.out.println(studentVO);
    }
//测试结果
Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:31:14 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
StudentVO(name=李四, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:31:14, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))

3.5.3 注意点

使用 “ . ” ,将一个嵌套的bean的值合并到一个扁平化的对象中。

3.6 对象更新

3.6.1 定义映射器的Java接口
    @Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "extendDto", target = ".")
//    @Mapping(source = "name",target = "name",  ignore = true)
    void updateStudent(Student student,@MappingTarget StudentVO studentVO);
    
    @Mapping(source = "student.birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "extendDto", target = ".")
    @Mapping(source = "name",target = "name",  ignore = true)
    void updateStudent(Student student,@MappingTarget StudentVO studentVO);
3.6.2 测试验证
public static void main(String[] args) {
        Student student = Student.builder()
                .name("李四")
                .age(16)
                .gender(GenderEnum.Male)
                .height(174.3)
                .birthday(new Date())
                .extendDto( ExtendDto.builder()
                        .email("[email protected]")
                        .phone("110")
                        .address("陕西")
                        .build())
                .build();
        StudentVO studentVO = StudentVO.builder()
                .name("王五")
                .age(20)
                .height(204.3)
                .birthday("2020-01-01 12:11:11")
                .extendDto( ExtendDto.builder()
                        .email("[email protected]")
                        .phone("110110119")
                        .address("陕西西安")
                        .build())
                .build();
        System.out.println("更新前 student:"+student.toString());
        System.out.println("更新前 studentVO:"+studentVO.toString());
        StudentMapper.INSTANCE.updateStudent(student,studentVO);

        System.out.println("更新后 student:"+student.toString());
        System.out.println("更新后 studentVO:"+studentVO.toString());
    }
//测试结果
更新前 :Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:56:35 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
更新前 :StudentVO(name=王五, age=20, gender=null, height=204.3, birthday=2020-01-01 12:11:11, extendDto=ExtendDto(email=dmjxsy@163.com, address=陕西西安, phone=110110119))
更新后 :Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:56:35 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
更新后 :StudentVO(name=李四, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:56:35, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
//添加 @Mapping(source = "name",target = "name",  ignore = true)
更新前 :Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:57:39 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
更新前 :StudentVO(name=王五, age=20, gender=null, height=204.3, birthday=2020-01-01 12:11:11, extendDto=ExtendDto(email=dmjxsy@163.com, address=陕西西安, phone=110110119))
更新后 :Student(name=李四, age=16, gender=Male, height=174.3, birthday=Thu Sep 28 15:57:39 CST 2023, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))
更新后 :StudentVO(name=王五, age=16, gender=Male, height=174.3, birthday=2023-09-28 15:57:39, extendDto=ExtendDto(email=dmjxsy@126.com, address=陕西, phone=110))

3.6.3 注意点

@MappingTarget 使用改注解标记目标对象我们就可以更新该对象的值。如果想不更新某个值,可以给加一个ignore = true的标签来忽略。

4、性能比较

4.1 Apache BeanUtils 与 MapStruct比较

 public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        for (int i = 0; i < 10; i++) {
            Long start = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentVO studentVO = new StudentVO();
                org.apache.commons.beanutils.BeanUtils.copyProperties(studentVO, student);
            }
            System.out.println("org.apache.commons.beanutils.BeanUtils第"+ i +"次执行---100W次转换耗时:" + (System.currentTimeMillis() - start));
            Long start2 = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentMapper.INSTANCE.student2StudentVO(student);
            }
            System.out.println("MapStruct第"+ i +"次执行-----100W次转换耗时:" + (System.currentTimeMillis() - start2));
        }
    }
//测试结果
org.apache.commons.beanutils.BeanUtils0次执行---100W次转换耗时:11854
MapStruct0次执行-----100W次转换耗时:1981
org.apache.commons.beanutils.BeanUtils1次执行---100W次转换耗时:10828
MapStruct1次执行-----100W次转换耗时:2080
org.apache.commons.beanutils.BeanUtils2次执行---100W次转换耗时:9894
MapStruct2次执行-----100W次转换耗时:1891
org.apache.commons.beanutils.BeanUtils3次执行---100W次转换耗时:9543
MapStruct3次执行-----100W次转换耗时:1735
org.apache.commons.beanutils.BeanUtils4次执行---100W次转换耗时:6862
MapStruct4次执行-----100W次转换耗时:1958

4.2 Hutool BeanUtil与 MapStruct比较

public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        for (int i = 0; i < 5; i++) {
            Long start = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentVO studentVO = new StudentVO();
                cn.hutool.core.bean.BeanUtil.copyProperties(studentVO,student,true);
            }
            System.out.println("cn.hutool.core.bean.BeanUtil第"+ i +"次执行---100W次转换耗时:" + (System.currentTimeMillis() - start));
            Long start2 = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentMapper.INSTANCE.student2StudentVO(student);
            }
            System.out.println("MapStruct第"+ i +"次执行-----100W次转换耗时:" + (System.currentTimeMillis() - start2));
        }
    }
//测试结果
cn.hutool.core.bean.BeanUtil0次执行---100W次转换耗时:7169
MapStruct0次执行-----100W次转换耗时:2538
cn.hutool.core.bean.BeanUtil1次执行---100W次转换耗时:4797
MapStruct1次执行-----100W次转换耗时:1707
cn.hutool.core.bean.BeanUtil2次执行---100W次转换耗时:5582
MapStruct2次执行-----100W次转换耗时:1505
cn.hutool.core.bean.BeanUtil3次执行---100W次转换耗时:4906
MapStruct3次执行-----100W次转换耗时:1322
cn.hutool.core.bean.BeanUtil4次执行---100W次转换耗时:3789
MapStruct4次执行-----100W次转换耗时:1635

4.3 Spring BeanUtils 与 MapStruct比较

    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        for (int i = 0; i < 5; i++) {
            Long start = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentVO studentVO = new StudentVO();
                org.springframework.beans.BeanUtils.copyProperties(studentVO, student);
            }
            System.out.println("org.springframework.beans.BeanUtils第"+ i +"次执行---100W次转换耗时:" + (System.currentTimeMillis() - start));
            Long start2 = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentMapper.INSTANCE.student2StudentVO(student);
            }
            System.out.println("MapStruct第"+ i +"次执行-----100W次转换耗时:" + (System.currentTimeMillis() - start2));
        }
    }
//测试结果
org.springframework.beans.BeanUtils0次执行---100W次转换耗时:1156
MapStruct0次执行-----100W次转换耗时:3376
org.springframework.beans.BeanUtils1次执行---100W次转换耗时:290
MapStruct1次执行-----100W次转换耗时:1367
org.springframework.beans.BeanUtils2次执行---100W次转换耗时:506
MapStruct2次执行-----100W次转换耗时:1586
org.springframework.beans.BeanUtils3次执行---100W次转换耗时:318
MapStruct3次执行-----100W次转换耗时:1319
org.springframework.beans.BeanUtils4次执行---100W次转换耗时:304
MapStruct4次执行-----100W次转换耗时:1226

4.4 Cglib BeanCopier 与 MapStruct比较

 public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        for (int i = 0; i < 5; i++) {
            Long start = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentVO studentVO = new StudentVO();
                org.springframework.cglib.beans.BeanCopier.create(StudentVO.class, Student.class, false);
            }
            System.out.println("org.springframework.cglib.beans.BeanCopier第"+ i +"次执行---100W次转换耗时:" + (System.currentTimeMillis() - start));
            Long start2 = System.currentTimeMillis();
            for (int i1 = 0; i1 < 1000000; i1++) {
                Student student = Student.builder()
                        .name("小明")
                        .age(6)
                        .gender(GenderEnum.Male)
                        .height(121.1)
                        .birthday(new Date())
                        .build();
                StudentMapper.INSTANCE.student2StudentVO(student);
            }
            System.out.println("MapStruct第"+ i +"次执行-----100W次转换耗时:" + (System.currentTimeMillis() - start2));
        }
    }
 //测试结果
org.springframework.cglib.beans.BeanCopier0次执行---100W次转换耗时:725
MapStruct0次执行-----100W次转换耗时:4524
org.springframework.cglib.beans.BeanCopier1次执行---100W次转换耗时:210
MapStruct1次执行-----100W次转换耗时:1663
org.springframework.cglib.beans.BeanCopier2次执行---100W次转换耗时:72
MapStruct2次执行-----100W次转换耗时:1446
org.springframework.cglib.beans.BeanCopier3次执行---100W次转换耗时:80
MapStruct3次执行-----100W次转换耗时:1482
org.springframework.cglib.beans.BeanCopier4次执行---100W次转换耗时:84
MapStruct4次执行-----100W次转换耗时:1441

4.4 性能对比结果(5次平均值)

工具类 执行1000 执行1w 执行10w 执行100w
Apache BeanUtils 103.2 323.3 1105.8 7227.8
Hutool BeanUtil 111 249.2 925.4 4420.6
Spring BeanUtils 80 93.4 206.4 728.6
Cglib BeanCopier 86.4 103.8 103.8 263
MapStruct 53.6 137.8 447.8 2672.2

你可能感兴趣的:(Java基础,Java优化,python,开发语言)