找回密码
 立即注册
首页 业界区 业界 PO、VO、BO、DTO、DAO、POJO傻傻分不清楚

PO、VO、BO、DTO、DAO、POJO傻傻分不清楚

豌笆 6 小时前
前言

最近有小伙伴问我:O、VO、BO、DTO、DAO、POJO有什么区别?
第一眼看到,你可能也会有点懵。
这些对象的概念很多,确实容易搞混。
今天这篇文章跟大家一起聊聊这6种对象的含义、职责、区别和常见的坑,希望对你会有所帮助。
一、6种对象的职责边界

对象设计的本质是关注点分离——每个对象只做一件事,且做好它!
1.1 PO

它的含义是Persistent Object,即持久化对象。

  • 职责:与数据库表严格1:1映射,仅承载数据存储结构
  • 特征

    • 属性与表字段完全对应
    • 无业务逻辑方法(仅有getter/setter)

  • 代码示例
  1. public class UserPO {  
  2.   private Long id;      // 对应表主键  
  3.   private String name;  // 对应name字段
  4. }  
复制代码
1.2 DAO

它的含义是Data Access Object,即数据访问对象。

  • 职责封装所有数据库操作(CRUD),隔离业务与存储细节
  • 特征

    • 接口方法对应SQL操作
    • 返回PO或PO集合

  • 代码示例
  1. public interface UserDao {  
  2.     // 根据ID查询PO  
  3.     UserPO findById(Long id);  
  4.    
  5.     // 分页查询  
  6.     List<UserPO> findPage(@Param("offset") int offset, @Param("limit") int limit);  
  7. }  
复制代码
底层原理:DAO模式 = 接口 + 实现类 + PO
1.png

1.3 BO

它的含义是Business Object,即业务对象。

  • 职责封装核心业务逻辑,聚合多个PO完成复杂操作
  • 特征

    • 包含业务状态机、校验规则
    • 可持有多个PO引用

  • 代码示例:订单退款BO
  1. public class OrderBO {  
  2.    // 主订单数据  
  3.    private OrderPO orderPO;  
  4.    // 子订单项  
  5.    private List<OrderItemPO> items;
  6.    
  7.    // 业务方法:执行退款  
  8.    public RefundResult refund(String reason) {     
  9.    if (!"PAID".equals(orderPO.getStatus())) {  
  10.       throw new IllegalStateException("未支付订单不可退款");
  11.    }  // 计算退款金额、调用支付网关等  }  
  12. }  
复制代码
1.4 DTO

它的含义是Data Transfer Object,即数据传输对象。

  • 职责跨层/跨服务数据传输,屏蔽敏感字段
  • 特征

    • 属性集是PO的子集(如排除password字段)
    • 支持序列化(实现Serializable)

  • 代码示例:用户信息DTO
  1. public class UserDTO implements Serializable {  
  2.     private Long id;  
  3.     private String name;  
  4. }  
复制代码
1.5 VO

它的含义是View Object,即视图对象。

  • 职责适配前端展示,包含渲染逻辑
  • 特征

    • 属性可包含格式化数据(如日期转yyyy-MM-dd)
    • 聚合多表数据(如订单VO包含用户名字)

  • 代码示例
  1. public class OrderVO {  
  2.   private String orderNo;  
  3.   private String createTime; // 格式化后的日期   private String userName;   // 关联用户表字段   
  4.   
  5.   //状态码转文字描述  
  6.   public String getStatusText() {  
  7.      return OrderStatus.of(this.status).getDesc();  
  8.   }  
  9. }  
复制代码
1.6 POJO

它的含义是Plain Old Java Object,即普通Java对象。

  • 职责基础数据容器,可扮演PO/DTO/VO角色
  • 特征

    • 只有属性+getter/setter
    • 无框架依赖(如不继承Spring类)

  • 典型实现:Lombok简化代码
  1. // 自动生成getter/setter  
  2. @Data
  3. public class UserPOJO {  
  4.   private Long id;  
  5.   private String name;  
  6. }  
复制代码
二、主流的对象流转模型

场景1

传统三层架构(DAO → DTO → VO)。
适用系统:后台管理系统、工具类应用
核心流程
2.png

代码示例:用户查询服务
  1. // Service层  
  2. public UserDTO getUserById(Long id) {  
  3.    UserPO userPO = userDao.findById(id); // 从DAO获取PO  
  4.    UserDTO dto = new UserDTO();  
  5.    dto.setId(userPO.getId());  
  6.    dto.setName(userPO.getName()); // 过滤敏感字段  
  7.    return dto; // 返回DTO  
  8. }  
  9. // Controller层  
  10. public UserVO getUser(Long id) {  
  11.    UserDTO dto = userService.getUserById(id);  
  12.    UserVO vo = new UserVO();  
  13.    vo.setUserId(dto.getId());  
  14.    vo.setUserName(dto.getName());  
  15.    vo.setRegisterTime(formatDate(dto.getCreateTime())); // 格式化日期  
  16.    return vo;  
  17. }  
复制代码
优点:简单直接,适合CRUD场景
缺点:业务逻辑易泄漏到Service层
场景2

DDD架构(PO → DO → DTO → VO)。
适用系统:电商、金融等复杂业务系统
核心流程
3.png

关键角色:DO(Domain Object)替代BO
代码示例:订单支付域
  1. // Domain层:订单领域对象  
  2. public class OrderDO {  
  3.    private OrderPO orderPO;  
  4.    private PaymentPO paymentPO;  
  5.    
  6.    // 业务方法:支付校验  
  7.    public void validatePayment() {  
  8.      if (paymentPO.getAmount() < orderPO.getTotalAmount()) {  
  9.         throw new PaymentException("支付金额不足");  
  10.       }
  11.    }  
  12. }  
  13. // App层:协调领域对象  
  14. public OrderPaymentDTO pay(OrderPayCmd cmd) {  
  15.     OrderDO order = orderRepo.findById(cmd.getOrderId());  
  16.     order.validatePayment(); // 调用领域方法  return OrderConverter.toDTO(order); // 转DTO  
  17. }  
复制代码
优点:业务高内聚,适合复杂规则系统
缺点:转换层级多,开发成本高
三、高效转换工具

手动转换对象?效率低且易错!
苏三在这里推荐三大利器。
3.1  MapStruct:编译期代码生成

原理:APT注解处理器生成转换代码
示例:PO转DTO
  1. @Mapper  
  2. public interface UserConverter {  
  3.    UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);  
  4.    
  5.    @Mapping(source = "createTime", target = "registerDate")  
  6.    UserDTO poToDto(UserPO po);  
  7. }  
  8. // 编译后生成UserConverterImpl.java  
  9. public class UserConverterImpl {  
  10.    public UserDTO poToDto(UserPO po) {  
  11.       UserDTO dto = new UserDTO();  
  12.       dto.setRegisterDate(po.getCreateTime()); // 自动赋值!  
  13.       return dto;  
  14.   }  
  15. }  
复制代码
优点:零反射损耗,性能接近手写代码
开源地址:https://github.com/mapstruct/mapstruct
3.2 Dozer + Lombok:注解驱动转换

组合方案

  • Lombok:自动生成getter/setter
  • Dozer:XML/注解配置字段映射
  1. // Lombok注解
  2. @Data
  3. public class UserVO {  
  4.    private String userId;  
  5.    private String userName;  
  6. }  
  7. // 转换配置  
  8. <field>  
  9.   userId  
  10.   <b>id</b>  
  11. </field>  
复制代码
适用场景:字段名不一致的复杂转换
3.3 手动Builder模式:精细控制

适用场景:需要动态构造的VO
  1. public class OrderVOBuilder {  
  2.    public OrderVO build(OrderDTO dto) {  
  3.      return OrderVO.builder()  
  4.         .orderNo(dto.getOrderNo())  
  5.         .amount(dto.getAmount() + "元") // 动态拼接  
  6.         .statusText(convertStatus(dto.getStatus()))  
  7.         .build();  
  8.     }  
  9. }  
复制代码
四、避坑指南

坑1:PO直接返回给前端
  1. // 致命错误:暴露数据库敏感字段!  
  2. public UserPO getUser(Long id) {  
  3.   // 返回的PO包含password  
  4.   return userDao.findById(id);
  5. }  
复制代码
解决方案

  • 使用DTO过滤字段
  • 注解屏蔽:@JsonIgnore
坑2:DTO中嵌入业务逻辑
  1. public class OrderDTO {
  2.     // 错误!DTO不应有业务方法  
  3.     public void validate() {
  4.        if (amount < 0)
  5.           throw new Exception();  
  6.     }  
  7. }  
复制代码
本质错误:混淆DTO与BO的职责
坑3:循环嵌套转换
  1. // OrderVO中嵌套List<ProductVO>  
  2. public class OrderVO {
  3.    // 嵌套对象  
  4.    private List<ProductVO> products;
  5. }  
  6. // 转换时触发N+1查询  
  7. orderVO.setProducts(order.getProducts()
  8.        .stream()  
  9.        .map(p -> convertToVO(p)) // 循环查询数据库  
  10.        .collect(toList()));  
复制代码
优化方案:批量查询 + 并行转换
五、如何选择对象模型?

4.png

总结

关于对象的4个核心原则:

  • 单一职责
    PO只存数据,BO只管业务,VO只负责展示——绝不越界!
  • 安全隔离

    • PO永不出DAO层(防数据库泄露)
    • VO永不出Controller(防前端逻辑污染服务)

  • 性能优先

    • 大对象转换用MapStruct(编译期生成代码)
    • 嵌套集合用批量查询(杜绝N+1)

  • 适度设计

    • 10张表以内的系统:可用POJO一撸到底
    • 百张表以上核心系统:必须严格分层

对象设计没有银弹,理解业务比套用模式更重要
当你在为对象命名纠结时,不妨回到业务的起点问一句:“它此刻的核心职责是什么?”
最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。
本文收录于我的技术网站:http://www.susan.net.cn

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册