在 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 是戶口本。