在 Java 项目里(尤其是 Spring、MyBatis 这类框架),经常会看到一堆以 O 结尾的类:VO、DO、DTO、BO、POJO……它们本质上都是普通的 Java Bean(即 POJO),但职责和出现的位置不同。下面用“用户下单”这个场景,把常见的几种 O 讲清楚。
1. DO(Data Object)
别名:Entity、PO(Persistent Object)。
出现位置:数据库访问层(DAO / Mapper)。
职责:
一张表对应一个 DO。
字段与列一一对应,命名也尽量保持和列名一致。
只做数据持久化,不带业务逻辑。
示例
@TableName("t_user") public class UserDO {private Long id;private String name;private String password; // 数据库里加密存储private LocalDateTime createTime;private LocalDateTime updateTime; }
2. DTO(Data Transfer Object)
出现位置:服务间或层间远程/进程间通信(Controller ↔ Service、Service ↔ 外部 RPC、消息队列)。
职责:
专门用来“搬运”数据,屏蔽内部实现细节。
当DO无法接收前端传来的全部数据时,使用DTO。
可以聚合多张 DO 的字段,也可以只取部分字段,避免把 DO 直接暴露出去。
序列化友好(JSON / Protobuf / Hessian)。
示例
public class UserDTO {private Long id;private String name;// 没有 password! }
3. VO(View Object / Value Object)
出现位置:展示层(前端页面、移动端、小程序、Thymeleaf、Vue/React)。
职责:
面向“页面渲染”或“接口返回”。
业务服务层,处理完数据,返回信息给前端,可以通过VO.
字段名、格式尽量贴近前端需求(驼峰、下划线、枚举值转中文)。
经常做脱敏、格式化、单位换算(钱→元/分、日期→yyyy-MM-dd)。
示例
public class UserVO {private Long userId; // id -> userIdprivate String nickName; // name -> nickNameprivate String createTimeStr; // LocalDateTime -> "2024-07-14 12:00" }
4. BO(Business Object)
出现位置:业务逻辑层(Service)。
职责:
把多张 DO 组合成一个业务意义上的对象。
可以包含业务方法(例如计算折扣、校验库存)。
很多团队偷懒直接用 DO 代替 BO,导致贫血模型。
示例
public class OrderBO {private UserDO buyer;private List<OrderItemDO> items;private CouponDO coupon;public BigDecimal calcPayAmount() { ... } }
5. POJO(Plain Old Java Object)
定义:上面所有 O 的统称。只要是一个“只有属性+getter/setter+toString”的简单 Java 类,都叫 POJO。
注意:POJO 不是某一种 O,而是它们的“祖宗”。
一张图总结(从下往上数据流动)
数据库表↓
DO(持久化)↓
DAO 层↓
Service 层(组装 DO → BO,做业务计算)↓
Manager / RPC 层(BO → DTO,远程传输)↓
Controller 层(DTO → VO,适配前端)↓
前端页面 / 小程序
常见疑问
问题 | 解答 |
---|---|
DO 和 Entity 有什么区别? | 没区别,只是叫法不同。JPA 喜欢叫 Entity,MyBatis 喜欢叫 DO/PO。 |
可以直接把 DO 返回给前端吗? | 不建议。一来字段可能多余(密码、逻辑删除),二来命名格式可能不符合前端习惯。 |
项目小,有必要分这么细吗? | 如果只有几个接口,全部用 XxxDTO 一把梭也行。但人多了、接口多了以后,分层对象能显著降低心智负担。 |
MapStruct / BeanUtils 干嘛用? | 做对象转换(DO→DTO→VO)的胶水代码,省掉手写 100 个 setter。 |
一句话记忆:
DO 存库,DTO 跑路,VO 露脸,BO 干活,POJO 是户口本。