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

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

大家好,我是苏三,又跟大家见面了。

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

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

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

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

最近建了一些工作内推群,各大城市都有,欢迎各位HR和找工作的小伙伴进群交流,群里目前已经收集了不少的工作内推岗位。

一、6种对象的职责边界对象设计的本质是关注点分离——每个对象只做一件事,且做好它!

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

职责:与数据库表严格1:1映射,仅承载数据存储结构特征:属性与表字段完全对应无业务逻辑方法(仅有getter/setter)代码示例:代码语言:javascript复制public class UserPO {

private Long id; // 对应表主键

private String name; // 对应name字段

}

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

职责:封装所有数据库操作(CRUD),隔离业务与存储细节特征:接口方法对应SQL操作返回PO或PO集合代码示例:代码语言:javascript复制public interface UserDao {

// 根据ID查询PO

UserPO findById(Long id);

// 分页查询

List findPage(@Param("offset") int offset, @Param("limit") int limit);

}

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

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

职责:封装核心业务逻辑,聚合多个PO完成复杂操作特征:包含业务状态机、校验规则可持有多个PO引用代码示例:订单退款BO代码语言:javascript复制public class OrderBO {

// 主订单数据

private OrderPO orderPO;

// 子订单项

private List items;

// 业务方法:执行退款

public RefundResult refund(String reason) {

if (!"PAID".equals(orderPO.getStatus())) {

throw new IllegalStateException("未支付订单不可退款");

} // 计算退款金额、调用支付网关等 }

}

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

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

private Long id;

private String name;

}

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

职责:适配前端展示,包含渲染逻辑特征:属性可包含格式化数据(如日期转yyyy-MM-dd)聚合多表数据(如订单VO包含用户名字)代码示例:代码语言:javascript复制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简化代码代码语言:javascript复制// 自动生成getter/setter

@Data

public class UserPOJO {

private Long id;

private String name;

}

二、主流的对象流转模型场景1传统三层架构(DAO → DTO → VO)。

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

核心流程:

代码示例:用户查询服务

代码语言:javascript复制// 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层

场景2DDD架构(PO → DO → DTO → VO)。

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

核心流程:

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

代码示例:订单支付域

代码语言:javascript复制// Domain层:订单领域对象

public class OrderDO {

private OrderPO orderPO;

private PaymentPO paymentPO;

// 业务方法:支付校验

public void validatePayment() {

if (paymentPO.getAmount() < orderPO.getTotalAmount()) {

throw new 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

代码语言:javascript复制@Mapper

public interface UserConverter {

UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);

@Mapping(source = "createTime", target = "registerDate")

UserDTO poToDto(UserPO po);

}

// 编译后生成UserConverterImpl.java

public class 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/setterDozer:XML/注解配置字段映射代码语言:javascript复制// Lombok注解

@Data

public class UserVO {

private String userId;

private String userName;

}

// 转换配置

userId

id

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

3.3 手动Builder模式:精细控制适用场景:需要动态构造的VO

代码语言:javascript复制public class OrderVOBuilder {

public OrderVO build(OrderDTO dto) {

return OrderVO.builder()

.orderNo(dto.getOrderNo())

.amount(dto.getAmount() + "元") // 动态拼接

.statusText(convertStatus(dto.getStatus()))

.build();

}

}

四、避坑指南坑1:PO直接返回给前端代码语言:javascript复制// 致命错误:暴露数据库敏感字段!

public UserPO getUser(Long id) {

// 返回的PO包含password

return userDao.findById(id);

}

解决方案:

使用DTO过滤字段注解屏蔽:@JsonIgnore坑2:DTO中嵌入业务逻辑代码语言:javascript复制public class OrderDTO {

// 错误!DTO不应有业务方法

public void validate() {

if (amount < 0)

throw new Exception();

}

}

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

坑3:循环嵌套转换代码语言:javascript复制// OrderVO中嵌套List

public class OrderVO {

// 嵌套对象

private List products;

}

// 转换时触发N+1查询

orderVO.setProducts(order.getProducts()

.stream()

.map(p -> convertToVO(p)) // 循环查询数据库

.collect(toList()));

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

五、如何选择对象模型?总结关于对象的4个核心原则:

单一职责:

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

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

✧ 相关推荐 ✧

自动挡汽车D/S/L档位使用指南:看完这篇,新手秒变老司机
童年记忆陨落!《快乐星球》“老顽童爷爷”赵克明去世,享年93岁,引发集体泪目!
对公账户一次可以提多少现金?
365游戏盒子

对公账户一次可以提多少现金?

📅 07-01 👁️ 8134