惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
有赞技术团队
有赞技术团队
J
Java Code Geeks
H
Hackread – Cybersecurity News, Data Breaches, AI and More
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Hugging Face - Blog
Hugging Face - Blog
人人都是产品经理
人人都是产品经理
酷 壳 – CoolShell
酷 壳 – CoolShell
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Check Point Blog
博客园 - 【当耐特】
The GitHub Blog
The GitHub Blog
Recent Announcements
Recent Announcements
The Cloudflare Blog
Microsoft Azure Blog
Microsoft Azure Blog
腾讯CDC
Vercel News
Vercel News
IT之家
IT之家
MyScale Blog
MyScale Blog
博客园_首页
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
罗磊的独立博客

博客园 - z5337

【转】Avalonia 12 最小信创项目模板 【转】Avalonia 周边介绍 【转】国产操作系统对 Avalonia 的支持情况 【转】国产操作系统优化篇 [openKylin] 入门知识点 【转】国产操作系统优化篇 [openKylin] 第一步 - Qt + VMware 【转】p2055dn 打印机配置 IP 地址 【转】Windows 锁屏后屏幕不熄灭排查 【转】既然 java 8 因为组件容易被攻击,.net core 也有大量第三方组件,是不是也容易出问题 【转】最小 web api 学习 【转】使用记事本区分图片格式 【转】蓝牙耳机的播放控制 【转】JoyToKey 键位映射 【转】[C#] 要从接口取时间,单个订单查询和批量查询,写一个接口还是两个接口合适? 【转】[C#] Web API 中的常见层次 【转】[C#] .net core 项目的目标框架设置 【转】[C#] 项目里的配置文件与选项对比 【转】[C#] GlobalUsing 的使用 【转】[.net core] 项目的 Windows 服务器部署 【转】《三字经》全文 【转】[IDEA] 调试时怎么判断使用哪个配置文件 【转】[web]怎么移除网页最上层的水印 【转】[C#] 30+个.NET开发最佳实践 【转】[C#] WPF 保留选中的高亮色 【转】Windows Server 系统的桌面上显示 此电脑 图标 【转】[C#] 参数前加 in 的作用 【转】[C#] Enum 的 Flags 特性 【转】[C#] Tuples 元组介绍 【转】[C#] Record 的使用 【转】[C#] 预处理指令
【转】PO、DO、VO、BO、DTO、DAO、POJO 的区别
z5337 · 2025-09-16 · via 博客园 - z5337

转自:https://mp.weixin.qq.com/s/MkeND4CzdDuGQ2KvcfgyXA

最近有小伙伴问我:PO、VO、BO、DTO、DAO、POJO有什么区别?

你第一眼看到,可能也会有点懵。

这些对象的概念很多,确实容易搞混。

今天这篇文章跟大家一起聊聊这6种对象的含义、职责、区别和常见的坑,希望对你会有所帮助。

一、6种对象的职责边界

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

1.1 PO

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

  • 职责:与数据库表严格1:1映射,仅承载数据存储结构
  • 特征
    • 属性与表字段完全对应
    • 无业务逻辑方法(仅有getter/setter)
  • 代码示例
public class UserPO {  
  private Long id;      // 对应表主键  
  private String name;  // 对应name字段
}  

1.2 DAO

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

  • 职责封装所有数据库操作(CRUD),隔离业务与存储细节
  • 特征
    • 接口方法对应SQL操作
    • 返回PO或PO集合
  • 代码示例
public interface UserDao {  
    // 根据ID查询PO  
    UserPO findById(Long id);  
    
    // 分页查询  
    List<UserPO> findPage(@Param("offset") int offset, @Param("limit") int limit);  
}  

底层原理:DAO模式 = 接口 + 实现类 + PO

图片

1.3 BO

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

  • 职责封装核心业务逻辑,聚合多个PO完成复杂操作
  • 特征
    • 包含业务状态机、校验规则
    • 可持有多个PO引用
  • 代码示例:订单退款BO
public class OrderBO {  
   // 主订单数据  
   private OrderPO orderPO;  
   // 子订单项  
   private List<OrderItemPO> items; 
   
   // 业务方法:执行退款  
   public RefundResult refund(String reason) {     
   if (!"PAID".equals(orderPO.getStatus())) {  
      throw new IllegalStateException("未支付订单不可退款"); 
   }  // 计算退款金额、调用支付网关等  }  
}  

1.4 DTO

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

  • 职责跨层/跨服务数据传输,屏蔽敏感字段
  • 特征
    • 属性集是PO的子集(如排除password字段)
    • 支持序列化(实现Serializable
  • 代码示例:用户信息DTO
public class UserDTO implements Serializable {  
    private Long id;  
    private String name;  
}  

1.5 VO

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

  • 职责适配前端展示,包含渲染逻辑
  • 特征
    • 属性可包含格式化数据(如日期转yyyy-MM-dd
    • 聚合多表数据(如订单VO包含用户名字)
  • 代码示例
public class OrderVO {  
  private String orderNo;  
  private String createTime; // 格式化后的日期   private String userName;   // 关联用户表字段    
  
  //状态码转文字描述  
  public String getStatusText() {  
     return OrderStatus.of(this.status).getDesc();  
  }  
}  

1.6 POJO

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

  • 职责基础数据容器,可扮演PO/DTO/VO角色
  • 特征
    • 只有属性+getter/setter
    • 无框架依赖(如不继承Spring类)
  • 典型实现:Lombok简化代码
// 自动生成getter/setter  
@Data 
public class UserPOJO {  
  private Long id;  
  private String name;  
}  

二、主流的对象流转模型

场景1

传统三层架构(DAO → DTO → VO)。

适用系统:后台管理系统、工具类应用

核心流程

图片

代码示例:用户查询服务

// Service层  
public UserDTO getUserById(Long id) {  
   UserPO userPO = userDao.findById(id); // 从DAO获取PO  
   UserDTO dto = new UserDTO();  
   dto.setId(userPO.getId());  
   dto.setName(userPO.getName()); // 过滤敏感字段  
   return dto; // 返回DTO  
}  

// Controller层  
public UserVO getUser(Long id) {  
   UserDTO dto = userService.getUserById(id);  
   UserVO vo = new UserVO();  
   vo.setUserId(dto.getId());  
   vo.setUserName(dto.getName());  
   vo.setRegisterTime(formatDate(dto.getCreateTime())); // 格式化日期  
   return vo;  
}  

优点:简单直接,适合CRUD场景

缺点:业务逻辑易泄漏到Service层

场景2

DDD架构(PO → DO → DTO → VO)。

适用系统:电商、金融等复杂业务系统

核心流程

图片

关键角色:DO(Domain Object)替代BO

代码示例:订单支付域

// Domain层:订单领域对象  
publicclass OrderDO {  
   private OrderPO orderPO;  
   private PaymentPO paymentPO;  
   
   // 业务方法:支付校验  
   public void validatePayment() {  
     if (paymentPO.getAmount() < orderPO.getTotalAmount()) {  
        thrownew PaymentException("支付金额不足");  
      } 
   }  
}  

// App层:协调领域对象  
public OrderPaymentDTO pay(OrderPayCmd cmd) {  
    OrderDO order = orderRepo.findById(cmd.getOrderId());  
    order.validatePayment(); // 调用领域方法  return OrderConverter.toDTO(order); // 转DTO  
}  

优点:业务高内聚,适合复杂规则系统

缺点:转换层级多,开发成本高

三、高效转换工具

手动转换对象?效率低且易错!

3.1  MapStruct:编译期代码生成

原理:APT注解处理器生成转换代码

示例:PO转DTO

@Mapper  
publicinterface UserConverter {  
   UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);  
   
   @Mapping(source = "createTime", target = "registerDate")  
   UserDTO poToDto(UserPO po);  
}  

// 编译后生成UserConverterImpl.java  
publicclass UserConverterImpl {  
   public UserDTO poToDto(UserPO po) {  
      UserDTO dto = new UserDTO();  
      dto.setRegisterDate(po.getCreateTime()); // 自动赋值!  
      return dto;  
  }  
}  

优点:零反射损耗,性能接近手写代码

开源地址:https://github.com/mapstruct/mapstruct

3.2 Dozer + Lombok:注解驱动转换

组合方案

  • Lombok:自动生成getter/setter
  • Dozer:XML/注解配置字段映射
// Lombok注解
@Data 
public class UserVO {  
   private String userId;  
   private String userName;  
}  

// 转换配置  
<field>  
  <a>userId</a>  
  <b>id</b>  
</field>  

适用场景:字段名不一致的复杂转换

3.3 手动Builder模式:精细控制

适用场景:需要动态构造的VO

public class OrderVOBuilder {  
   public OrderVO build(OrderDTO dto) {  
     return OrderVO.builder()  
        .orderNo(dto.getOrderNo())  
        .amount(dto.getAmount() + "元") // 动态拼接  
        .statusText(convertStatus(dto.getStatus()))  
        .build();  
    }  
}  

四、避坑指南

坑1:PO直接返回给前端

// 致命错误:暴露数据库敏感字段!  
public UserPO getUser(Long id) {  
  // 返回的PO包含password  
  return userDao.findById(id); 
}  

解决方案

  • 使用DTO过滤字段
  • 注解屏蔽:@JsonIgnore

坑2:DTO中嵌入业务逻辑

public class OrderDTO {
    // 错误!DTO不应有业务方法  
    public void validate() 
       if (amount < 0
          throw new Exception();  
    }  
}  

本质错误:混淆DTO与BO的职责

坑3:循环嵌套转换

// OrderVO中嵌套List<ProductVO>  
public class OrderVO {
   // 嵌套对象  
   private List<ProductVO> products; 
}  

// 转换时触发N+1查询  
orderVO.setProducts(order.getProducts()
       .stream()  
       .map(p -> convertToVO(p)) // 循环查询数据库  
       .collect(toList()));  

优化方案:批量查询 + 并行转换

五、如何选择对象模型?

图片

总结

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

  1. 单一职责: PO只存数据,BO只管业务,VO只负责展示——绝不越界!
  2. 安全隔离
    • PO永不出DAO层(防数据库泄露)
    • VO永不出Controller(防前端逻辑污染服务)
  3. 性能优先
    • 大对象转换用MapStruct(编译期生成代码)
    • 嵌套集合用批量查询(杜绝N+1)
  4. 适度设计
    • 10张表以内的系统:可用POJO一撸到底
    • 百张表以上核心系统:必须严格分层

对象设计没有银弹,理解业务比套用模式更重要

当你在为对象命名纠结时,不妨回到业务的起点问一句:“它此刻的核心职责是什么?”