⚡效率工具⚡ - 推荐一款对象映射神器「MapStruct」|8月更文挑战

前言

工做中经常出现的一种状况是,咱们须要把Entity/PO/DTO/VO/QueryParam之间作转换,解决这类问题的工具备不少,如OrikaBeanUtilsHutool工具包,为什么对MapStrucet情有独钟,用来单独推荐呢?html


简介

MapSturct 是一个生成类型安全, 高性能且无依赖的 JavaBean 映射代码的注解处理器java

怎么理解呢,对于BeanUtils来讲,映射主要是靠反射来实现,当有大量的拷贝时,意味着大量的使用了反射,效率相对低下,就连《阿里巴巴开发手册》中也明确提到,不许使用BeanUtilsgit

众所周知,效率最快的固然是手写的get()、set(),固然开发效率也是最慢的,而MapStruct经过编译器编译生成常规的方法,咱们经过写接口和注解就能够手动帮咱们生成get()、set()代码,效率不知提升了多少倍github

具体性能测试已经有人作过了,能够参考这篇文章:5种常见Bean映射工具的性能比对spring


示例

首先引入MapStruct的依赖apache

Maven:安全

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.4.2.Final</version>
</dependency>
复制代码

在Plugins加上:markdown

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <annotationProcessorPaths>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.4.2.Final</version>
            </path>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.12</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>
复制代码

现有两个实体类app

@Data
public class Student {
    String name;
    Integer age;
    String idCard;
    Date birthDay;
}
复制代码
@Data
public class StudentVO {
    String name;
    String birthDay;
    String idCard;
    Integer studentAge;
}
复制代码

场景1:单个对象之间的映射 / 批量映射

按照需求,咱们要把Student对象转换为StudentVO对象,其中student.age 要映射到studentVO.studentAge中,而 student.idCard在此次查询中无需显示,student.birthDay格式化成字符串再传入studentVO.bidthDay框架

咱们只须要建立一个接口:

@Component
@Mapper(componentModel = "spring")
public interface StudentConverter {
    
    @Mapping(target = "studentAge", source = "age")
    @Mapping(target = "idCard", ignore = true)
    @Mapping(target = "birthDay", dateFormat = "yyyy-MM-dd HH:mm:ss")
    StudentVO studentToStudentVO(Student student);
    
    List<StudentVO> studentToStudentVO(List<Student> students);
}
复制代码

使用:

@RestController
public class TestController {

    @Autowired
    private StudentConverter studentConverter;

    @GetMapping("/test")
    public void beanConvertTest() {
        Student student = new Student();
        student.setName("name");
        student.setAge(18);
        student.setIdCard("123456");
        student.setBirthDay(new Date());

        StudentVO studentVO = studentConverter.studentToStudentVO(student);
        System.out.println(studentVO);

        List<Student> students = Collections.singletonList(student);
        List<StudentVO> studentVOS = studentConverter.studentToStudentVO(students);
        System.out.println(studentVOS);
    }
}
复制代码

输出结果:

能够看到出生日期被成功被格式化成咱们想要的字符串,idCard咱们不须要因而没有被映射


场景2:多个对象映射为一个

有时候会出现要将多个对象映射为一个对象的状况,而多个对象之间可能有重复的字段,这个根据@Mapping注解就能灵活解决

如今新增一个类Address.class,其中name字段和student.name是重复字段

@Data
public class Address {
    String name;
    String address;
}
复制代码

需求是在场景1的基础上,将address字段加到StudentVO.class中,可是name字段得用Student.class 的。

@Data
public class StudentVO {
    String name;
    String birthDay;
    String idCard;
    Integer studentAge;
    //新增字段
    String address;
}
复制代码

咱们能够经过source参数来设置须要映射的字段来自于哪一个实例,接口方法以下:

@Mapping(target = "studentAge", source = "student.age")
@Mapping(target = "idCard", ignore = true)
@Mapping(target = "birthDay", source = "student.birthDay", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping(target = "name", source = "student.name")
StudentVO studentAndAddressToVO(Student student, Address address);
复制代码

使用:

@GetMapping("/test")
public void beanConvertTest() {
    Student student = new Student();
    student.setName("name");
    student.setAge(18);
    student.setIdCard("123456");
    student.setBirthDay(new Date());

    Address address = new Address();
    address.setName("addressName");
    address.setAddress("address");

    StudentVO studentVO = studentConverter.studentAndAddressToVO(student, address);
    System.out.println(studentVO);
}
复制代码

输出结果:

对于上面的例子,须要注意的是,多个对象映射为一个对象时,只有重复的字段,或者字段名不同的字段进行映射,才须要用注解告诉MapStruct,到底要使用哪一个来源的字段

更多更复杂的用法能够参考官方示例仓库,很是齐全:mapstruct/mapstruct-examples: Examples for using MapStruct (github.com)


其余注意事项

  • 若是项目中也同时使用到了 Lombok,必定要注意 Lombok的版本要等于或者高于1.18.10,不然会有编译不经过的状况发生
  • 若是接口的存放包名为mapper,可能与Mybatis冲突会致使项目起不来
  • 当两个对象属性不一致时,好比Student对象中某个字段不存在于StudentVO当中时,在编译时会有警告提示,能够在@Mapping中配置 ignore = true,当字段较多时,能够直接在@Mapper中设置unmappedTargetPolicy属性或者unmappedSourcePolicy属性为 ReportingPolicy.IGNORE便可

总结:应该如何选择对象映射工具

  1. 如果字段少,写起来也不麻烦,就不必用框架了,手写get()/set() 就行了,技术不该该为用而用,应该为了解决对应的问题而用
  2. 如果字段多,转换不频繁,为省事就用BeanUtils吧,稍微复杂点的场景也可使用 Hutool工具包BeanUtils,提供的拷贝选项要多一些
  3. 字段多又转换频繁,从性能方面考虑,仍是从高性能的工具中选一个用吧,好比本文推荐的MapStruct

Reference

经常使用开发库 - MapStruct工具库详解 | Java 全栈知识体系 (pdai.tech)

丢弃掉那些BeanUtils工具类吧,MapStruct真香!!! (juejin.cn)

相关文章
相关标签/搜索