news 2026/4/23 11:02:27

别再混淆了!PO、VO、BO、DTO、DAO、POJO 一文彻底搞懂

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
别再混淆了!PO、VO、BO、DTO、DAO、POJO 一文彻底搞懂

👉这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:

  • 《项目实战(视频)》:从书中学,往事中“练”

  • 《互联网高频面试题》:面朝简历学习,春暖花开

  • 《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题

  • 《精进 Java 学习指南》:系统学习,互联网主流技术栈

  • 《必读 Java 源码专栏》:知其然,知其所以然

👉这是一个或许对你有用的开源项目

国产Star破10w的开源项目,前端包括管理后台、微信小程序,后端支持单体、微服务架构

RBAC权限、数据权限、SaaS多租户、商城、支付、工作流、大屏报表、ERP、CRMAI大模型、IoT物联网等功能:

  • 多模块:https://gitee.com/zhijiantianya/ruoyi-vue-pro

  • 微服务:https://gitee.com/zhijiantianya/yudao-cloud

  • 视频教程:https://doc.iocoder.cn

【国内首批】支持 JDK17/21+SpringBoot3、JDK8/11+Spring Boot2双版本

来源:

  • 一张图看懂数据怎么流

  • POJO:所有对象的白板底座

  • PO:数据库长什么样,它就长什么样

  • DAO:数据库的接线员

  • BO:业务规则活在这里

  • DTO:跨边界的搬运工

  • VO:前端要什么就给什么

  • 一次请求的完整对象流转

  • 什么时候该分、什么时候不必


第一次打开公司项目,包结构里一堆类:OrderPOOrderVOOrderDTOOrderBO……同一个订单,为什么要写四个类?

做了几个项目才搞明白:不是为了显得复杂,是数据在系统里每流过一层,关心的东西就不一样。数据库关心字段映射,业务层关心逻辑判断,前端关心显示格式。一个类全干,改一处就崩一片。

一张图看懂数据怎么流

三层架构对象分布图:表现层用VO,业务逻辑层用BO和DTO,数据持久层用PO和DAO,POJO作为所有对象的基础形态

数据从数据库到用户屏幕,经过三道转手:

  • 持久层:PO 对应数据库表,DAO 负责 CRUD

  • 业务层:BO 执行业务逻辑,DTO 在层与层之间搬运数据

  • 表现层:VO 把数据格式化成前端想要的样子

每个对象的边界明确,改一层不影响另一层。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

POJO:所有对象的白板底座

Plain Old Java Object——普通的 Java 对象,不继承框架基类,不实现框架接口,没有任何注解依赖。

// 这就是一个 POJO:干净、独立、不依赖任何框架 public class Order { private Long id; private Long userId; private BigDecimal amount; private Integer status; private LocalDateTime createTime; // getter / setter 省略 }

PO 是加了@Entity的 POJO,DTO 是加了Serializable的 POJO,VO 是专门用于展示的 POJO。POJO 是白纸,加上不同的用途约束就变成了其他对象。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

PO:数据库长什么样,它就长什么样

Persistent Object。一张t_order表对应一个OrderPO,字段名、类型严格映射:

@Entity @Table(name = "t_order") publicclass OrderPO { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "user_id", nullable = false) private Long userId; @Column(name = "total_amount", precision = 10, scale = 2) private BigDecimal totalAmount; @Column(name = "status") private Integer status; // 0-待支付 1-已支付 2-已取消 3-已退款 @Column(name = "pay_time") private LocalDateTime payTime; @Column(name = "create_time", updatable = false) private LocalDateTime createTime; // getter / setter }

MyBatis 项目里 PO 不需要 JPA 注解,就是个干净的 POJO,映射关系写在 XML 里。

最常见的错误:Controller 直接返回 PO。后果——密码、盐值、删除标记等敏感字段直接暴露给前端;数据库改个字段名,前端立刻崩。

DAO:数据库的接线员

Data Access Object——封装所有数据库操作,让业务层看不到 SQL。

// JPA 风格:接口 + 方法名自动推导 SQL @Repository public interface OrderDAO extends JpaRepository<OrderPO, Long> { List<OrderPO> findByUserIdAndStatus(Long userId, Integer status); @Query("SELECT o FROM OrderPO o WHERE o.totalAmount > :minAmount AND o.createTime > :since") List<OrderPO> findLargeOrders(@Param("minAmount") BigDecimal minAmount, @Param("since") LocalDateTime since); }
// MyBatis 风格:接口 + XML 映射 public interface OrderMapper { OrderPO selectById(@Param("id") Long id); List<OrderPO> selectByUserId(@Param("userId") Long userId); int insert(OrderPO order); int updateStatus(@Param("id") Long id, @Param("status") Integer status, @Param("updateTime") LocalDateTime updateTime); }

DAO 的价值:今天用 MySQL,明天换 PostgreSQL,业务代码一行不改。数据源切换只影响 DAO 实现层。

BO:业务规则活在这里

Business Object——承载业务逻辑的对象,活在 Service 层,可能由多个 PO 组合而成。

"能不能下单"这个判断不属于 PO(PO 只管存数据),也不属于 DAO(DAO 只管读写),它属于 BO:

public class OrderBO { private OrderPO order; private UserPO user; private List<OrderItemPO> items; private CouponPO coupon; /** * 判断订单是否可以发起退款 * 规则:已支付 + 未发货 + 下单不超过7天 */ public boolean canRefund() { if (order.getStatus() != 1) returnfalse; // 非已支付 if (order.getShipTime() != null) returnfalse; // 已发货 return Duration.between(order.getCreateTime(), LocalDateTime.now()).toDays() <= 7; } /** * 计算实付金额:商品总价 - 优惠券抵扣 */ public BigDecimal calculatePayAmount() { BigDecimal itemTotal = items.stream() .map(i -> i.getPrice().multiply(BigDecimal.valueOf(i.getQuantity()))) .reduce(BigDecimal.ZERO, BigDecimal::add); if (coupon != null && coupon.isValid()) { return itemTotal.subtract(coupon.getDiscount()).max(BigDecimal.ZERO); } return itemTotal; } }

实际项目里 BO 有时候就是@Service类本身,有时候是 Service 内部的中间对象。不用纠结形式,核心是:业务判断放这一层,不散落到 Controller 和 DAO 里。

DTO:跨边界的搬运工

Data Transfer Object——在层之间、服务之间搬运数据,只搬不算。

微服务时代 DTO 用得极多:A 服务调 B 服务,不可能把整个 PO 扔过去(字段暴露 + 耦合)。DTO 只暴露必要字段:

// 服务间传输用的 DTO,只包含对方需要的字段 publicclass OrderDTO implements Serializable { privatestaticfinallong serialVersionUID = 1L; private Long orderId; private Long userId; private String userName; // 跨服务带过来的冗余字段 private BigDecimal payAmount; private String statusDesc; // "已支付",不是 Integer 1 private LocalDateTime createTime; // 没有密码、没有内部状态码、没有关联对象 // getter / setter }
// 请求参数也常用 DTO 封装 public class CreateOrderDTO { @NotNull(message = "商品ID不能为空") private Long productId; @Min(value = 1, message = "数量至少为1") private Integer quantity; private Long couponId; // 可选 // getter / setter }

DTO 通常实现Serializable(走网络得序列化),结构扁平,不含业务方法。

VO:前端要什么就给什么

Value Object——离用户最近的对象,所有字段按前端需要来组织:

public class OrderVO { private Long id; private String orderNo; // "2024061800001" private String statusText; // "待发货",不是 Integer 2 private String statusColor; // "#FF6B00",前端直接渲染 private String totalAmountDisplay; // "¥ 299.00" private String createTimeDisplay; // "2024年6月18日 14:30" private String payTimeDisplay; // "3分钟前"(相对时间) private List<OrderItemVO> items; // 嵌套的商品列表 VO private Boolean canCancel; // 按钮是否可点 private Boolean canRefund; // getter / setter }

数据库存的是status = 2total_amount = 299.00create_time = 2024-06-18 14:30:22。前端不需要知道这些原始值——VO 直接给格式化好的字符串、计算好的状态。

VO 和 DTO 最容易混淆:DTO 强调"传输",是中间载体,任意层都能用;VO 强调"展示",是终点站,只给前端。

一次请求的完整对象流转

对象转换链路图:从数据库经PO→BO→DTO→VO,最终到达前端,DAO负责数据库与PO之间的桥接

以"查询订单详情"为例:

前端请求 → Controller 接收参数 → Service 调 DAO.selectById() 拿回 OrderPO → PO 组装成 BO,执行业务判断(canRefund? canCancel?) → BO 转成 DTO 返回给 Controller → Controller 把 DTO 转成 VO(格式化时间、金额、状态文案) → 返回前端

每一步转换的理由:

  • PO 不暴露前端 →安全

  • BO 不跨服务传输 →解耦

  • DTO 不带业务方法 →职责清晰

  • VO 不对应数据库结构 →前后端独立演进

什么时候该分、什么时候不必

项目规模

建议

个人/Demo 项目

一个 Model 走天下,别折腾

3-5 人小项目

PO + VO 就够,中间层按需加

多人协作/微服务

严格分层,PO/BO/DTO/VO 全套

判断标准:如果你改了一个数据库字段,前端 API 也得跟着改——说明你需要分层了。如果服务 A 改了内部逻辑,服务 B 也得重新编译——说明你的 DTO 边界没划好。

分层的本质不是为了"看起来专业",是为了改一处不牵动全身。当项目只有你一个人维护的时候,这些分层只是负担。但只要超过三个人协作,这套规矩就是省心的选择。


欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,长按”或“扫描”下方二维码噢

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

文章有帮助的话,在看,转发吧。 谢谢支持哟 (*^__^*)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 10:59:18

从抓包到报告:手把手教你用Wireshark过滤并导出关键证据数据包

从抓包到报告&#xff1a;Wireshark高级取证操作全流程指南 当服务器突然出现异常流量激增&#xff0c;或是API响应时间莫名延长时&#xff0c;网络数据包往往藏着最直接的证据。作为安全工程师&#xff0c;我曾用Wireshark从数十GB的流量中精准提取出3个关键数据包&#xff0c…

作者头像 李华
网站建设 2026/4/23 10:57:02

Real Anime Z技术解析:智能权重清洗注入机制如何解决前缀不兼容问题

Real Anime Z技术解析&#xff1a;智能权重清洗注入机制如何解决前缀不兼容问题 1. 项目概述 Real Anime Z是一款基于阿里云通义Z-Image底座模型开发的高精度二次元图像生成工具。该工具通过Real Anime Z专属微调权重优化&#xff0c;专门针对真实系二次元风格进行深度调优&a…

作者头像 李华
网站建设 2026/4/23 10:56:51

Oracle EBS 的汇兑损益核心设计逻辑:子模块(AP/AR/CM)逐笔确认 “已实现汇兑损益”,总账(GL)期末集中重估确认 “未实现汇兑损益”,并支持标准冲回机制,严格区分已实现 / 未实现、

Oracle EBS 的汇兑损益核心设计逻辑&#xff1a;子模块&#xff08;AP/AR/CM&#xff09;逐笔确认 “已实现汇兑损益”&#xff0c;总账&#xff08;GL&#xff09;期末集中重估确认 “未实现汇兑损益”&#xff0c;并支持标准冲回机制&#xff0c;严格区分已实现 / 未实现、交…

作者头像 李华
网站建设 2026/4/23 10:56:40

告别文献混乱!用JabRef 5.10建立你的个人学术知识库(附WinEdt联动配置)

科研效率革命&#xff1a;用JabRef构建智能文献知识库的完整实践指南 当你的电脑桌面堆满未命名的PDF文件&#xff0c;当你在深夜写作时突然找不到上周读过的那篇关键论文&#xff0c;当合作者向你索要某领域的研究资料你却需要花费半天时间整理——这些场景是否似曾相识&#…

作者头像 李华
网站建设 2026/4/23 10:56:34

Beads(信息学奥赛一本通- P1461) [POI 2010] KOR-Beads(洛谷-P3498)

【题目描述】Zxl有一次决定制造一条项链&#xff0c;她以非常便宜的价格买了一长条鲜艳的珊瑚珠子&#xff0c;她现在也有一个机器&#xff0c;能把这条珠子切成很多块&#xff08;子串&#xff09;&#xff0c;每块有k&#xff08;k>0&#xff09;个珠子&#xff0c;如果这…

作者头像 李华