您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

使用MapStruct,让Bean对象之间转换更简单

时间:2022-02-23 10:46:17  来源:  作者:小黑说Java

场景

通常,在后端项目开发中,因为会有项目分层的设计,例如MVC架构,以及最近很火热的DDD架构中,会在不同的层级,有对应的DO,BO,VO,DTO等各种各样的POJO类,而我们在层级之间进行调用的数据传递时,通常要进行对象属性之间的映射。对于一些简单的对象,可能会直接使用get,set方法完成,或者使用BeanUtils工具类来完成属性之间的映射。

这些代码往往是枯燥、无聊的,并且在不同的业务处理类中可能需要重复地对两个对象进行互相转换。导致代码里充斥着大量的get,set转换,如果使用BeanUtils,可能会因为字段名称不一致,导致在运行时才能发现问题。

那有没有什么方案能解决这个问题呢?

答案就是使用MapStruct,可以优雅地解决上面的这些问题。

MapStruct是一种代码生成器组件,它遵循约定优于配置的原则,可以让我们的Bean对象之间的转换变得更简单。

为什么要使用MapStruct?

如前文中描述,在多层应用设计中,需要在不同的对象模型之间进行转换,属性映射,手动编写这些代码不仅繁琐,而且很容易出错,MapStruct的目的是让这项工作变得简单,自动化。

相比其他的映射框架,比如BeanUtils,或者Json序列化反序列化等方式,MapStruct能在编译时就生成映射,确保程序运行性能,并且能在编译时就发现错误。

MapStruct怎么用?

MapStruct本质上是一个注解处理器,可以直接在Maven或Gradle等编译工具中集成。

以Maven为例,我们需要先在依赖中添加MapStruct依赖,并将mapstruct-processor配置在maven插件中。

<properties>
    <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
</properties>

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>${org.mapstruct.version}</version>
</dependency>

<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>${org.mapstruct.version}</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

接下来,便可以在代码中使用MapStruct。

同名字段映射

比如,我们现在有一个功能是要从持久层查询学生对象Student,然后将它转换为StudentDTO传递给业务层。这里我们需要在Student对象和StudentDTO对象之间进行转换。

Student.JAVA

@Data
public class Student{
 private int no;
 private String name;
}

StudentDTO.java

@Data
public class StudentDTO {
 private int no;
 private String name;
}

针对这种对象之间的转换,我们需要创建一个MApper类进行映射。

@Mapper(componentModel = "spring")
public interface StudentMapper {
    StudentDTO toDto(Student student);
}

@Mapper :是MapStruct的注解,用于创建和生成映射的实现。

componentModel = "spring":该属性的含义是将StudentMapper的实例作为Spring的Bean对象,放在Spring容器中,这样就可以在其他业务代码中方便的注入。

因为Student和StudentDTO的属性名相同,所以我们不需要任何其他代码显式映射。

不同名字段映射

假如DTO和PO之间的字段名称不同,应该如何处理呢?

Student.java

@Data
public class Student{
    private int no;
    private String name;
    private String gender;
}

StudentDTO.java

@Data
public class StudentDTO {
    private int no;
    private String name;
    private String sex;
}

如上代码所示,在Student和StudentDTO中,性别字段的名称不一致。要实现这种情况的映射,只需要添加如下的@Mapping注解。

@Mapper(componentModel = "spring")
public interface StudentMapper {
     @Mapping(source = "gender", target = "sex")
    StudentDTO toDto(Student student);
}

自定义对象属性映射

假如每个学生有自己的地址信息Address,那么该如何处理呢?

源对象类

@Data
public class Student{
    private int no;
    private String name;
    private String gender;
    private Address address;
}

@Data
public class Address{
    private String city;
    private String province;
}

目标对象类

@Data
public class StudentDTO{
    private int no;
    private String name;
    private String sex;
    private AddressDTO address;
}

@Data
public class AddressDTO{
    private String city;
    private String province;
}

这种要对内部对象进行映射,我们需要对内部对象也创建一个Mapper,然后将内部对象的Mapper导入到StudentMapper中。

@Mapper(componentModel = "spring")
public interface AddressMapper {
    AddressDTO toDto(Address address);
}

@Mapper(componentModel = "spring",uses = {AddressMapper.class})
public interface StudentMapper {
    @Mapping(source = "gender", target = "sex")
    StudentDTO toDto(Student student);
}

自定义转换逻辑映射

如果在对象转换时,不仅是简单的属性之间的映射,还需要按照某种业务逻辑进行转换,比如每个Student中的地址信息Address,在StudentDTO中只需要地址信息中的city。

源对象类

@Data
public class Student{
    private int no;
    private String name;
    private String gender;
    private Address address;
}

@Data
public class Address{
    private String city;
    private String province;
}

目标对象类

@Data
public class StudentDTO{
    private int no;
    private String name;
    private String sex;
    private String city;
}

针对这种情况,我们可以直接在source中使用address.city,也可以通过自定义方法来完成逻辑转换。

@Mapper(componentModel = "spring",uses = {AddressMapper.class})
public interface StudentMapper {
    @Mapping(source = "gender", target = "sex")
    // @Mapping(source = "address.city", target = "city")
    @Mapping(source = "address", target = "city",qualifiedByName = "getAddressCity")
    StudentDTO toDto(Student student);

    @Named("getAddressCity")
    default String getChildCircuits(Address address) {
        if(address == null) {
            return "";
        }
        return address.getCity();
    }
}

集合映射

使用MapStruct进行集合字段的映射也很简单。比如每个Student中有多门选修的课程List<Course>,要映射到StudentDTO中的List<CourseDTO>中。

@Mapper(componentModel = "spring")
public interface CourseMapper {
    CourseDTO toDto(Course port);

    List<CourseDTO> toCourseDtoList(List<Course> courses);
}

@Mapper(componentModel = "spring",uses = {CourseMapper.class})
public interface StudentMapper {
    @Mapping(source = "gender", target = "sex")
    @Mapping(source = "address", target = "city",qualifiedByName = "getAddressCity")
    CircuitDto toDto(Circuit circuit);
}

其他特殊情况映射

除了常见的对象映射外,有些情况我们可能需要在转换时设置一些固定值,假设StudentDTO中有学历字段degree,但是暂时该数据还未录入,所以这里要设置默认值“未知”。可以使用@Mapping注解完成。

@Mapping(target = "degree", constant = "未知")

@BeforeMapping和@AfterMapping

@BeforeMapping和@AfterMapping是两个很重要的注解,看名字基本就可以猜到,可以用来在转换之前和之后做一些处理。

比如我们想要在转换之前做一些数据验证,集合初始化等功能,可以使用@BeforeMapping;

想要在转换完成之后进行一些数据结果的修改,比如根据更加StudentDTO中选修课程List<CourseDTO>的数量来给是否有选修课字段haveCourse设置布尔值等。

@Mapper(componentModel = "spring",uses = {PortMapper.class})
public interface StudentMapper {
    @BeforeMapping
    default void setCourses(Student student) {
        if(student.getCourses() == null){
            student.setCourses(new ArrayList<Course>());
        }
    }

    @Mapping(source = "gender", target = "sex")
    StudentDTO toDto(Student student);

    @AfterMapping
    default void setHaveCourse(StudentDTO studentDto) {
        if(studentDto.getCourses()!=null && studentDto.getCourses() >0){
            studentDto.setHaveCourse(true);
        }
    } 
}

总结

在本文中介绍了如何使用MapStruct来优雅的实现对象属性映射,减少我们代码中的get,set代码,避免对象属性之间映射的错误。在以上示例代码中可以看出,MapStruct大量使用了注解,让我们可以轻松完成对象映射。

如果你想了解更多MapStruct相关信息,可以继续阅读官方的文档。MapStruct官方文档



Tags:MapStruct   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
场景通常,在后端项目开发中,因为会有项目分层的设计,例如MVC架构,以及最近很火热的DDD架构中,会在不同的层级,有对应的DO,BO,VO,DTO等各种各样的POJO类,而我们在层级之间进行调用的数...【详细内容】
2022-02-23  Tags: MapStruct  点击:(0)  评论:(0)  加入收藏
▌简易百科推荐
场景通常,在后端项目开发中,因为会有项目分层的设计,例如MVC架构,以及最近很火热的DDD架构中,会在不同的层级,有对应的DO,BO,VO,DTO等各种各样的POJO类,而我们在层级之间进行调用的数...【详细内容】
2022-02-23  小黑说Java    Tags:MapStruct   点击:(0)  评论:(0)  加入收藏
前端部分md5.js md5加密var KEY = "COURSE";/* * Configurable variables. You may need to tweak these to be compatible with * the server-side, but the defaults wor...【详细内容】
2022-02-23  It健身    Tags:java   点击:(3)  评论:(0)  加入收藏
在上文Java自定义DNS解析器实践中,我们没有讲到org.apache.http.conn.DnsResolver具体如何实现负载均衡,今天我们就分享一下,负载均衡的具体实现。InMemoryDnsResolver被淘汰首...【详细内容】
2022-02-11  FunTester    Tags:DNS解析   点击:(17)  评论:(0)  加入收藏
Java 中的字节码,英文名为 bytecode, 是 Java 代码编译后的中间代码格式。JVM 需要读取并解析字节码才能执行相应的任务。从技术人员的角度看,Java 字节码是 JVM 的指令集。JV...【详细内容】
2022-02-10  记得要让着本宝宝    Tags:Java   点击:(20)  评论:(0)  加入收藏
日志是发现错误和调试代码的便捷工具。除了日志的功能方面,从 Java 安全的角度来看,日志也很重要, 当发生安全漏洞时,你的日志文件是寻找所发生事件线索的第一个位置。 日志的质...【详细内容】
2022-01-26  粤嵌教育    Tags:Java 日志   点击:(25)  评论:(0)  加入收藏
和软件的MACD的值进行对比过,数值是一样的,不过我这个版本小数点后面更精确,软件的是四舍五入的。这个版本支持增量更新,更加方便。使用需要单例模式,如果多并发请自行修改代码。...【详细内容】
2022-01-20  吴彬的分享    Tags:JAVA   点击:(35)  评论:(0)  加入收藏
1、阿里云DNS的SDK依赖<dependency> <groupId>com.aliyun</groupId> <artifactId>tea-openapi</artifactId> <version>0.0.19</version></dependency><dependency> <groupId...【详细内容】
2022-01-19  顶顶架构师    Tags:阿里云DNS   点击:(28)  评论:(0)  加入收藏
Maven是Java的项目配置管理工具,用来管理依赖,具体的用途就不展开说了。大部分项目,配置一个镜像仓库地址就可以了(单个mirror)。但是有的网上下载的项目需要从多个仓库查找对应...【详细内容】
2022-01-14  阿福ChrisYuan    Tags:Maven配置   点击:(25)  评论:(0)  加入收藏
闰年闰年(Leap Year)是为了弥补因人为历法规定造成的年度天数与地球实际公转周期的时间差而设立的。补上时间差的年份为闰年。闰年共有366天(1月~12月分别为31天、29天、31天、...【详细内容】
2022-01-11  3班的黄同学    Tags:   点击:(34)  评论:(0)  加入收藏
目录4、TCP网络传输的基本流程二、网络编程套接字(socket)5、cookie和session的用法6、基本实现http协议的代码四、传输层协议TCP和UDP4、TCP和UDP之间的对比六、数据链路层和...【详细内容】
2022-01-04  顶顶架构师    Tags:JAVA   点击:(47)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条