后端 | 对象装换工具MapStruct介绍

285 阅读6分钟

对象装换工具MapStruct介绍

对象转换工具 MapStruct 库,以安全优雅的方式来减少我们的转换代码,使用非常方便。 对象转换工具 MapStruct 是在编译期的时候实现代码装换,没有运行时依赖,故其执行的效率很高。

领域模型

DO、VO、DTO、PO

  1. 分层领域模型规约:

    • DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
    • DTO(Data Transfer Object):数据传输对象,ServiceManager 向外传输的对象。
    • BO(Business Object):业务对象,由 Service 层输出的封装业务逻辑的对象。
    • AO(Application Object):应用对象,在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
    • VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
    • Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输
  2. 领域模型命名规约:

    • 数据对象:xxxDOxxx即为数据表名
    • 数据传输对象:xxxDTOxxx为业务领域相关的名称。
    • 展示对象:xxxVOxxx一般为网页名称。
    • POJODO/DTO/BO/VO的统称,禁止命名成xxxPOJO
  3. 领域模型中的实体类可细分为4种类型:VO、DTO、DO、PO

    • PO(Persistant Object):持久化对象,表示持久层的数据结构;
    • DO( Data Object): 领域对象,即业务实体对象;
    • DTO( Data Transfer Object): 数据传输对象,用于展示层与服务层之间的数据传输对象,因此可以将DTO看成一个组合版的DO;
    • VO( View Object): 视图对象,用于展示层视图状态对象的对象。
    • 从分层角度来看:PO、DO/DTO、VO分别属于持久层、服务层和展现层。

PO :(persistant object ),持久对象

可以看成是与数据库中的表相映射的java对象。使用Hibernate来生成PO是不错的选择。

VO :(value object) ,值对象

通常用于业务层之间的数据传递,和PO一样也是仅仅包含数据而已。但应是抽象出的业务对象,可以和表对应,也可以不,这根据业务的需要。

  • PO只能用在数据层,VO用在商业逻辑层和表示层。各层操作属于该层自己的数据对象,这样就可以降低各层之间的耦合,便于以后系统的维护和扩展。

DAO :(Data Access Objects) ,数据访问对象接口

DAO是Data Access Object数据访问接口,数据访问:顾名思义就是与数据库打交道。夹在业务逻辑与数据库资源中间。

  1. J2EE开发人员使用数据访问对象(DAO)设计模式把底层的数据访问逻辑和高层的商务逻辑分开.实现DAO模式能够更加专注于编写数据访问代码。

  2. DAO模式是标准的J2EE设计模式之一.开发人员使用这个模式把底层的数据访问操作和上层的商务逻辑分开.一个典型的DAO实现有下列几个组件:

    • 一个DAO工厂类;
    • 一个DAO接口;
    • 一个实现DAO接口的具体类;
    • 数据传递对象(有些时候叫做值对象)。
    • 具体的DAO类包含了从特定的数据源访问数据的逻辑。

BO :(Business Object),业务对象层

表示应用程序领域内“事物”的所有实体类。这些实体类驻留在服务器上,并利用服务类来协助完成它们的职责。

DTO Data Transfer Object数据传输对象

主要用于远程调用等需要大量传输对象的地方。比如我们一张表有100个字段,那么对应的PO就有100个属性。但是我们界面上只要显示10个字段,客户端用WEB service来获取数据,没有必要把整个PO对象传递到客户端,这时我们就可以用只有这10个属性的DTO来传递结果到客户端,这样也不会暴露服务端表结构.到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为VO

POJO :(Plain Old Java Objects),简单的Java对象

实际就是普通JavaBeans,使用POJO名称是为了避免和EJB混淆起来, 而且简称比较直接.其中有一些属性及其getter、setter方法的类,有时可以作为value object或dto(Data Transform Object)来使用。

一般处理方法

  1. 使用set方法转换
  • 如果对象属性比较多时,需要写很多的set代码,较为繁琐,影响代码观感。
  1. 使用现有的工具类进行转换,比如常见的 BeanUtil.copyPoroperties("targetObject", "sourceObject")
  • 代码简洁来很多,但是因为其使用了反射,需要运行时依赖,性能不太好,在高并发的场景下并不常采用

MapStruct简介

  • MapStruct是一个注解处理器,用于生成类型安全、高性能和无依赖的Bean映射代码,其基于约定优于配置方法极大地简化了Java bean类型之间映射的实现。

    • 基于注解
    • 在编译期自动生成映射转换代码,这是代码性能提升的关键
    • 类型安全、高性能、无依赖性

MapStruct 使用方法

  1. 引入依赖
...
<properties>
    <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
</properties>
...
<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>
...
<build>
    <plugins>
        <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>
    </plugins>
</build>
...
  1. 创建相关的转换对象

    • 模拟从前端传入的DeviceParam对象,将该对象转为和数据库对应的DO
@Data
public class DeviceParam {
    // 设备id
    private Long applianceId;
    
    // 设备名字
    private String name;
}
@Data
public class DeviceDO {
    // 设备id
    private Long applianceId;
    
    // 设备名字
    private String name;
}
  1. 创建转换方法

    • 采用来@Mapper注解的方式来实现来两个对象数据的装换
@Mapper
public interface DeviceMapper {
​
  DeviceMapper INSTANCE = Mappers.getMapper(DeviceMapper.class);
​
  DeviceDO toDO(DeviceParam param);
}
  1. 使用
DeviceParam param = new DeviceParam();
param.setApplianceId("123456");
param.setName("客厅电视");
// 调用方式
DeviceDO deviceDO = DeviceMapper.INSTANCE.toDO(param);
// 打印
System.out.println(deviceDO.toString);

原理浅析

定义的转换器是接口类型的,而在`Java`规范中,接口只是负责功能定义,并不能直接提供功能操作,具体干活的还得需要其实现类,接着找实现类。
​
将以上的注解配置经过编译后,发现,每个接口都有个实现类,那就是MapStruct帮忙我们实现了接口
public class DeviceMapperImpl implements DeviceMapper {
    @Override
    public DeviceDO toDO(DeviceParam param) {
        if(param == null){
            return null;
        }
        DeviceDO deivceDO = new DeviceDO();
        if(param.getApplianceId() != null){
            deviceDO.setApplianceId(param.getApplianceId());
        }
        if(param.getName() != null){
            deviceDO.setName(param.getName());
        }
        return deviceDO;
    }
}

其他使用场景

- 当对象属性类型相同但是属性名称不一样时,通过 @Mapping 注解来手动指定转换。
@Data
public class DeviceParam {
    // 设备id
    private Long applianceId;
    
    // 设备名字
    private String deviceName;
}
@Data
public class DeviceDO {
    // 设备id
    private Long applianceId;
    
    // 设备名字
    private String name;
}
@Mapper
public interface DeviceMapper {
​
  DeviceMapper INSTANCE = Mappers.getMapper(DeviceMapper.class);
​
  @Mapping(source = "deviceName", target = "name")
  DeviceDO toDO(DeviceParam param);
}