文章目录

  • 前言
  • 一、解释VO/PO/DTO
  • 二、实际使用
  • PO的使用
  • 接口新增时-VO的使用
  • 接口修改时-VO的使用
  • 查询时-VO的使用
  • DTO的使用
  • 三、总结



前言

经常有人不明白VO/PO/DTO是干嘛的,不知道在实际项目中如何使用,本文通过代码展开介绍,耐心看完你就能明白VO/PO/DTO到底是干嘛的。


提示:以下是本篇文章正文内容,下面案例可供参考

一、解释VO/PO/DTO

名称

详情

VO

主要用来和前端交互,如接收前端参数,返回给查询前端结果

PO

对应数据库的表结构,不可修改,直接从数据库查出来的对象

DTO

作为service/manager层修改/查询数据库时的入参参数

二、实际使用

接下来我们以创建/修改/查询/同步订单为例子来实际使用VO/PO/DTO。

PO的使用

PO的作用:对应数据库的表结构,不可修改,直接从数据库查出来的对象

首先我们要创建一个订单。
创建两个类Order和OrderItem 这两个类就是PO,对应数据的表不可修改

@Data
public class Order{
    // 订单id
    private Long id;
    // 订单号
    private String uniqueOrderNo;
    // 购买人手机
    private String phone;
}
@Data
public class OrderItem {
    // 订单明细id
    private Long id;
    // 订单id
    private Long orderId;
    // 商品id
    private Long productId;
    // 购买数量
    private Integer quantity;
}

接下来我们模拟一下前端保存订单。


接口新增时-VO的使用

先创建实体类来接收前端的参数

@Data
public class OrderAddVo {
    // 订单号
    private String uniqueOrderNo;
    // 购买人手机
    private String phone;
    // 订单明细
    private List<OrderItemAddVo> items;
}
@Data
public class OrderItemAddVo {
    // 商品id
    private Long productId;
    // 购买数量
    private Integer quantity;
}

保存接口

@PostMapping(value =  "/add")
    public ResponseEnvelope<Long>(@RequestBody OrderAddVo addVo){
    	// 保存到数据库
        Long orderId = save(addVo);
        return new ResponseEnvelope<>(orderId);
    }

可以看到controller里面的add方法传了一个OrderAddVo -这个实体类就是VO,那么为什么是AddVo难道还有修改的editVo吗?修改的时候不能公用吗?
没错确实还有editVo。


接口修改时-VO的使用

下面我们来修改订单

@Data
public class OrderEditVo {
    // 订单id
    private Long id;
    // 购买人手机
    private String phone;
    // 订单明细
    private List<OrderItemEditVo> items;
}
@Data
public class OrderItemEditVo {
    // 订单明细id
    private Long id;
    // 商品id
    private Long productId;
    // 购买数量
    private Integer quantity;
}

修改接口

@PostMapping(value =  "/edit")
    public ResponseEnvelope<Long>(OrderEditVo editVo){
        Long orderId = edit(editVo);
        return new ResponseEnvelope<>(orderId);
    }

为什么我们不用之前的OrderAddVo呢? 我们观察OrderEditVo 类去掉了uniqueOrderNo订单号(一般不予许修改订单号),增加了订单id。
OrderItemEditVo 增加了订单明细id。
EditVo和AddVO参数在传递时是有区别的,有一些需要传递有些不需要。
我们修改/新增的接口尽量对应一个唯一的VO,这样才能清楚知道前端具体会传递哪些参数。

那只有新增和修改能用到VO吗? 我们看一下查询接口,现在需要提供一个根据id查询订单和订单明细的接口。


查询时-VO的使用

查询时VO的作用:

  1. 将数据库多个表的数据组装成一个VO返回给前端
  2. 数据脱敏,不直接返回数据库的表接口,需要的字段才在VO中指定。
  3. 不返回数据库所有的字段,只返回前端需要的字段。

查询接口订单明细接口

@Data
public class OrderDetailVo {
	// 订单id
	private String id;
	// 订单号
	private String uniqueOrderNo;
	// 订单明细
	private List<OrderDetailItemVo> items;
}
@Data
public class OrderDetailItemVo {
    // 订单明细id
    private Long id;
    // 订单id
    private Long orderId;
    // 商品
    private String productName;
    // 商品id
    private Long productId;
    // 购买数量
    private Integer quantity;
}

查询接口

@GetMapping(value ="/get")
    public ResponseEnvelope<OrderDetailVo>(Long id){
        List<OrderDetailItemVo> itemsVos = new ArrayList<>();

        // 查询数据库订单
        Order order = getOrder(id);
        // 查询数据库订单明细
        List<OrderItem> items = getOrderItem(id);
        items.forEach(item -> {
            OrderDetailItemV v = new OrderDetailItemV();
            BeanUtils.copyProperties(item, v);
            // 查询商品名称
            v.setProductName(getProductName(item.getProductId));
            itemsVos.add(v);
        });
        OrderDetailVo detailVo = new OrderDetailVo();
        // 复制属性
        BeanUtils.copyProperties(order, detailVo);
        detailVo.setItems(itemsVos);
        return new ResponseEnvelope<>(orderId);
    }

可以看到在查询方法里面执行了-查询商品名称,因为数据库的OrderItem 并没有保存商品名称字段,实际上很多时候我们都只是在数据库里引用了另一个表的id,前端需要的时候的时候我们会再去别的表查出来set给VO对象。
返回的OrderDetailVo 并没有phone字段,因为我们不希望暴露客户的手机号。


DTO的使用

为什么要使用DTO呢?

DTO的作用:
对于不同数据格式但是行为一致的,提供统一的方法入口

假设我们有个需要要求同步第三方的订单过来,第三方数据格式如下 。
一个订单对应一个ThirdPartyOrder

@Data
public class ThirdPartyOrder {
    private Order order;
    private List<OrderItem> items;
    private OrderPay orderPay;
}
private static class Order{
    // 订单号
    private String uniqueOrderNo;
    // 购买人手机
    private String phone;
}
private static class OrderItem{
    // 商品id
    private String productId;
    // 数量
    private Integer quantity;
}
private static class OrderPay{
    // 支付类型
    private String payType;
    // 支付流水号
    private String panNo;
}

第三方订单,原来的add接口的区别在于数据结构不一致,并且多出支付信息OrderPay orderPay。
这样的数据结构会导致我们没有办法使用save(OrderAddVo addVo)方法,同样的保存逻辑只是数据结构有一点点差异。
所以这个时候要用到DTO,定义一个save(OrderAddDto addDto),这个方法就是通用的保存订单的方法。

  1. 前端调用save(OrderAddDto addDto)方法时候需要转换实体,OrderAddVo-》OrderAddDto
  2. 第三方订单调用save(OrderAddDto addDto)方法时候需要转换实体,ThirdPartyOrder -》OrderAddDto

三、总结

po对应数据库表

新增时- addVo

@PostMapping(value =  "/add")
public ResponseEnvelope<Long>(@RequestBody OrderAddVo addVo)

修改时-editVo

@PostMapping(value =  "/edit")
public ResponseEnvelope<Long>(OrderEditVo editVo)

查询时-vo

@GetMapping(value ="/get")
public ResponseEnvelope<OrderDetailVo>(Long id)

通用方法-dto

save(OrderAddDto addTdo)

提示:每个公司在使用时可能名称不一样,但是其目的都是是一样的,无非就是规范接口入参出差,抽出通用方法

  1. 数据表有个对应的类
  2. 接收前端入参对应一个类
  3. 响应前端对应一个类
  4. service层/manager层通用方法一个类

转换实体工具类使用spring自带的BeanUtils.copyProperties(source,target)
注意:对象中有字段是其他实体类,或者List<对象> list 是不会对其属性复制的;