• MapStruct介绍以及VO、DTO、PO、DO的区别


    一.基本概念

    1.1VO**(Value Object)值对象**

    • 定义:

      VO就是展示用的数据,是让人看到的

    • VO和DTO的区别:

      VO会对DTO中的值,在展示时,赋予业务解释

      举例:

      PO中
      {"id":23,"gender":"男", "age":35}
      
      DTO可能是这样的(相较于PO,仅需要一些字段的值)
      { "gender":"男", "age":35 }
      
      
      对于业务一来说只需要性别,而且因为是一个古风聊天室,也不能直接展示男,因此经过业务解释业务一的VO是
      { "gender":"公子" }
      对于业务二来说只需要年龄,而且不需要精确的年龄,因此经过业务解释业务二的VO是
      { "age":"30~39" }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      可以:用dto来接收前端参数,vo返回给前端数据

    1.2DTO**(Data Transfer Object)数据传输对象**

    • 定义

      在前端,他的存在形式通常是js里面的对象(也可以简单理解成json),也就是通过ajax请求的那个数据体。

      在后端,他的存在形式是java对象,也就是在controller里面定义的那个东东,通常在后端不需要关心怎么从json转成java对象的

      json从前端传递到后端,然后json->java对象给后端;java对象在后端 -> json形式传递给前端

    • 服务和服务之间调用的传输对象能叫DTO吗:

      如果服务和服务之间相对独立,那就可以叫DTO;

      如果服务和服务之间不独立,每个都不是一个完整的业务模块,拆开可能仅仅是因为计算复杂度或者性能的问题,只能是BO

    • 使用sop:

      用 DTO 的好处有两个,一是能避免传递过多的无用数据,提高数据的传输速度;

      二是能隐藏后端的表结构。常见的用法是:将请求的数据或属性组装成一个 RequestDTO,再将响应的数据或属性组装成一个 ResponseDTO

      接收前端传递的数据使用DTO、展示给前端的数据使用VO

    1.3 PO**(Persistant Object)持久对象=**等同于Entity,这俩概念是一致的 或DO

    • 定义:

      简单说PO就是数据库中的记录,一个PO的数据结构对应着库中表的结构,表中的一条记录就是一个PO对象

    • 特点

      通常PO里面除了get,set之外没有别的方法

    1.4 BO(Business Object)业务对象

    • 定义:

      1.BO就是PO的组合,每份简历都包括教育经历、项目经历等,我们可以让教育经历和项目经历分别对应一个 PO,这样在我们建立对应求职简历的 BO 对象处理简历的时候,让每个 BO 都包含这些 PO 即可。(在形成BO期间即简历初版之前,可以对教育经历PO做一些计算、处理(比如:PO中时间为2011-2016就读于NJUPT,则形成BO过程中,可做计算本科时间5年 = 2016-2011)。简历初版BO中许多内容其实是不需要的,没必要展示在简历上,比如教育经历PO中你的老师是谁,BO中也有,这些无需展示在简历上。这样优化后的简历即为DTO。然后DTO仍然不优美,可以继续优化成最终简历VO(比如:DTO中学历展示为:研究生字段,VO中可以转换为 硕士字段)

      PO -> BO -> DTO(如果和前端交互可以用VO)

      比如我们有一个交易订单表,含有 25 个字段,那么其对应的 PO 就有 25 个属性,但我们的页面上只需要显示 5 个字段,因此没有必要把整个 PO 对象传递给客户端,这时我们只需把仅有 5 个属性的 DTO 把结果传递给客户端即可,而且如果用这个对象来对应界面的显示对象,那此时它的身份就转为 VO。

      2.比如处理一个人的业务逻辑,该人会睡觉,吃饭,工作,上班等等行为,还有可能和别人发关系的行为,处理这样的业务逻辑时,我们就可以针对BO去处理。

    • 特点:

      BO是一个业务对象,一类业务就会对应一个BO,数量上没有限制,而且BO会有很多业务操作,也就是说除了get,set方法以外,BO会有很多针对自身数据进行计算的方法

      BO来源:由PO拼装而成;Mybatis跳过PO直接生成BO

    • 与DTO的区别

      这两个的区别主要是就是字段的删减

      BO可能会含有很多接口对外所不需要的数据,因此DTO需要在BO的基础上,只要自己需要的数据,然后对外提供在这个关系上,DTO通常不会有数据内容的变化(原本展示:23pcs (1箱子 + 3件))补:1箱20件

      内容变化(现在一箱10件)要么在BO内部业务计算的时候完成,要么在解释VO的时候完成(现在业务展示需要发生变化,但是不在DTO计算,而是在BO进行计算, 13 = 1 * 10 + 3 ),对外展示还是这个字段。内部计算逻辑在BO完成

    1.5注意

    • DTO 和 BO

      工具类的系统和一些业务不是很复杂的系统DTO是可以和BO合并成一个,当业务扩展的时候注意拆分就行

      PO -> DTO(如果和前端交互可以用VO)

    • DTO和VO

      VO是可以第一个优化掉的,展示业务不复杂的可以压根儿不要,直接用DTO

    二.MapStruct

    参考:http://www.tianshouzhi.com/api/tutorials/mapstruct/291

    import org.mapstruct.Mapper;
    import org.mapstruct.Mapping;
    import org.mapstruct.Mappings;
    import org.mapstruct.factory.Mappers;
    
    import java.util.List;
    import java.util.Objects;
    
    @Mapper(componentModel = "spring")
    public interface MyrConverter {
    
        MyrConverter INSTANCE = Mappers.getMapper(MyrConverter.class);
            
    //事例1
        @Mappings({
                @Mapping(target = "billNo", source = "a.billNo"),
            
                @Mapping(target = "billType", expression = "java(null!=a.getBillType() ? (byte) a.getBillType().getCode() : null)"),
    
                @Mapping(target = "createAt", expression = "java(null!=a.getCreateAt() ? a.getCreateAt().getTime() : null)"),
        B convert2B(A a);
        List<B> convert2BList(List<A> sources);
    
            
            
    //事例2
        @Mappings({
                @Mapping(target = "outboundBillNo", source = "useBizBillNo"),
                @Mapping(target = "shipperId", source = "poiId"),
                @Mapping(target = "receiverId", source = "useReciverId"),
                @Mapping(target = "takeDate", expression = "java(getTakeDate(a))")
        })
        B convert2B(A a);
        List<B> convert2BList(List<A> aList);
    
        default Long getPlanTakeDate(A a) {
            if (Objects.nonNull(a.getTakeDate())) {
                return a.getTakeDate().getTime();
            }
            return null;
        }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    三.转换

    2.1 前置

    • Source:需要被转换的类

    • Target:期望转换成的类

    2.2 source类中字段,少于Target类中字段 & 二者相同的属性名称,可直接转换

    • souce类中字段:

      Long poiId

      String containerCode

      @Data
      public class Souce {
          private Long poiId;
          private String containerCode;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      target类字段:

      Long poiId

      String containerCode

      List*<Byte>* statusList;

    @Data
    public class Target {
        private Long poiId;
        private String containerCode;
        private List<Byte> statusList;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • source类中字段少于target类中字段,且相同的两个属性名称一样。可直接转换,无需@mapping()

      Target convert2Target(Source source);
      
      • 1

    2.3 source类中字段 == 目标类中字段 & 二者所有字段名称完全一样可直接转换

    • source

      B

    • target

      A

    • A-> B& 二者属性完全一致,且属性名称也相同

    B convert2B(A source);
    
    • 1

    2.4 source类中 有个别属性名称 和 target中不一致。需要@mapping()来帮助完成映射

       @Mappings({
                @Mapping(target = "taskList", source = "source.pickingTaskList"),
        })
        B convert2B(A source);
    
    • 1
    • 2
    • 3
    • 4

    2.5 source类中,有一个属性名称和target类中某个属性名称不一致。(但是他们的业务含义是一样的、类型也是一样的,都是list)

    AB
    Long skuIdLong skuId
    List*<DTO>* dtoList;List*<VO>* voList;
    • 这时,为了将A-> B,即需要Mappings()

    2.6 source中字段类型 和 target中字段类型不一致 & 类型一致但是需要做逻辑判断后再赋值

    • 字段类型不一致,需要express表达式转换
        @Mappings({
                @Mapping(target = "containerStatusDesc", expression = "java(null!=source.getUseStatus() ? source.getUseStatus().getDesc() : \"\")"),
                
                @Mapping(target = "shippedAt", expression = "java(null!=source.getShippingAt() ? source.getShippingAt().getTime() : null)"),
                
                @Mapping(target = "totalSkuQuantity", expression = "java(null == source.getTotalQuantity() ? \"0\" : source.getTotalQuantity().stripTrailingZeros().toPlainString())")
        })
      B convert2B(A source);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • source中字段类型为:Date shippingAt

    • target你要转换的类中相同业务含义的属性为: Long shippedAt

    • 所以,需要判断一下,若Date shippingAt不为null即null!=containerUseFlow.getShippingAt(),则将Date先转换为long,再赋值给Long shippedAt 否则为null

    • source中字段类型为:BigDecimal totalQuantity;

    • target你要转换的类中相同业务含义的属性为:String totalSkuQuantity;

    • 所以,需要判断一下,若BigDecimal totalQuantity;不为null,则将BigDecimal换为String,即.stripTrailingZeros().toPlainString()

      若为null,则赋值 “0” 应该是0的意思

    三.实现方式

    3.1 List -> List

    • convert2Dto()
    • 定义getPlanTakeDate()
    • 在接口中实现default getPlanTakeDate()方法即可
    @Mappings(value = {
            @Mapping(target = "planTakeDate", expression = "java(getPlanTakeDate(source))")
         
        })
        B convert2Dto(A source);
        List<B> convert2Dtos(List<A> sources);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.2 Target和Source中两个字段类型一致,但是名称不一致,转换时可直接写二者的名称

    @Mapping*(target = “totalPcs”, source = “skuPcs”)*

    3.3 两个字段名称一致但类型不一致,需要java方法进行转换

    @Mapping*(*target = "planTakeDate", expression = "java(getPlanTakeDate(source.planTakeDate))"*)*
    
    • 1

    3.4 两个字段类型一致,名称一致。不需要认为处理

    3.5 日期格式转换

        @MdpCopying(target = "ctime", dateFormat = "yyyy年MM月dd日HH:mm:ss", source = "ctime")
        @MdpCopying(target = "utime", dateFormat = "yyyy年MM月dd日HH:mm:ss", source = "utime")
        B copy2DO(A source);
        List<B> copy2DOs(List<A> source);
    
    • 1
    • 2
    • 3
    • 4

    四.QA

    4.1 convert2XXX(param1,param2);

    MapStruct 可以自动为您执行此操作。然而,它不能处理多参数方法(原则上它将源映射到目标)。所以报错:返回类型 java.util.List 是一个抽象类或接口。提供非抽象/非接口结果类型或工厂方法。

    解决:

    参考文档:

    https://zhuanlan.zhihu.com/p/102389552

  • 相关阅读:
    CleanMyMac X4.14.1最新版本下载
    WPF livecharts 折线图遮挡数字问题
    洗衣洗鞋小程序干洗店洗衣店上门取衣门店管理系统开发定制
    又一个新项目完结,炸裂!
    电子学会2022年9月青少年软件编程(图形化)等级考试试卷(三级)答案解析
    MTU与TCP-MSS
    Linux下的多线程编程
    Linux:syslog()的使用和示例
    深度学习之视频分类项目小记
    栈和队列——有效括号
  • 原文地址:https://blog.csdn.net/tmax52HZ/article/details/133217249