一篇文章讓你徹底搞懂Java中VO、DTO、BO、DO、PO
深入淺出講解各層對(duì)象區(qū)別+實(shí)戰(zhàn)應(yīng)用+代碼對(duì)比,告別概念混淆,設(shè)計(jì)出更優(yōu)雅的系統(tǒng)架構(gòu)!
“新手最大的噩夢(mèng):一個(gè)Java項(xiàng)目里,滿(mǎn)眼都是XxxVO、XxxDTO、XxxBO、XxxDO、XxxPO…” ??
是不是經(jīng)常被這些相似的概念搞得頭暈眼花?
- 為什么一個(gè)User要定義成UserVO、UserDTO、UserPO好幾個(gè)類(lèi)?
- 它們看起來(lái)字段都差不多,直接用一個(gè)User類(lèi)不行嗎?
- 到底什么時(shí)候該用VO?什么時(shí)候該用DTO?
別慌!這是每個(gè)Java程序員成長(zhǎng)的必經(jīng)之路! 今天,我就用最通俗易懂的方式,幫你徹底理清這些"O"的區(qū)別和作用,讓你從此告別混淆,設(shè)計(jì)出專(zhuān)業(yè)、規(guī)范的系統(tǒng)架構(gòu)! ??
一、 為什么需要這么多"O"?—— 核心思想:職責(zé)分離
在回答具體區(qū)別之前,首先要明白一個(gè)核心思想:軟件工程中最重要的原則之一就是"關(guān)注點(diǎn)分離"(Separation of Concerns)。
想象一個(gè)餐廳的后廚: ??
- 采購(gòu)員 買(mǎi)回來(lái)的原始食材 = PO(原始數(shù)據(jù))
- 廚師 處理后的半成品食材 = BO(業(yè)務(wù)處理后的數(shù)據(jù))
- 服務(wù)員 端給顧客的精致菜品 = DTO/VO(展示給外界的數(shù)據(jù))
如果讓采購(gòu)員直接端著一筐土豆給顧客,或者讓廚師去前臺(tái)結(jié)賬,會(huì)怎樣? 同樣的道理,在軟件架構(gòu)中,不同層次應(yīng)該處理不同的數(shù)據(jù)形態(tài)。
二、 各層對(duì)象詳解(附代碼對(duì)比)
1. PO(Persistent Object)持久化對(duì)象
作用: 與數(shù)據(jù)庫(kù)表結(jié)構(gòu)直接映射的Java對(duì)象,專(zhuān)用于數(shù)據(jù)持久化層(DAO/Mapper層)。
特點(diǎn):
- 與數(shù)據(jù)庫(kù)表一一對(duì)應(yīng),每個(gè)字段對(duì)應(yīng)表中的一個(gè)列
- 通常包含ORM框架注解(如MyBatis的
@Table、JPA的@Entity) - 不應(yīng)該包含業(yè)務(wù)邏輯
示例代碼:
// 對(duì)應(yīng)數(shù)據(jù)庫(kù)表 `user`
import javax.persistence.*;
import java.util.Date;
@Entity
@Table(name = "user")
public class UserPO {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@Column(name = "email")
private String email;
@Column(name = "create_time")
private Date createTime;
@Column(name = "update_time")
private Date updateTime;
// 只有g(shù)etter/setter,沒(méi)有業(yè)務(wù)方法
// getter/setter...
}
使用場(chǎng)景: MyBatis、JPA等ORM框架操作數(shù)據(jù)庫(kù)時(shí)使用。
2. DO(Domain Object)領(lǐng)域?qū)ο?/h3>
作用: 在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)中,代表業(yè)務(wù)領(lǐng)域中的核心實(shí)體,包含業(yè)務(wù)邏輯和數(shù)據(jù)。
特點(diǎn):
- 聚焦業(yè)務(wù)領(lǐng)域,不一定與數(shù)據(jù)庫(kù)表完全對(duì)應(yīng)
- 可以包含業(yè)務(wù)方法(這是與PO的最大區(qū)別?。?/li>
- 是業(yè)務(wù)邏輯的核心載體
示例代碼:
// 領(lǐng)域?qū)ο?- 包含業(yè)務(wù)邏輯
public class UserDO {
private Long id;
private String username;
private String password;
private String email;
private Integer status; // 狀態(tài):1-正常,2-禁用
private Integer loginAttempts; // 登錄嘗試次數(shù)
// 包含業(yè)務(wù)方法!
public boolean isLocked() {
return loginAttempts >= 5; // 嘗試5次以上被鎖定
}
public void incrementLoginAttempts() {
this.loginAttempts++;
}
public void resetLoginAttempts() {
this.loginAttempts = 0;
}
public boolean validatePassword(String inputPassword) {
// 密碼驗(yàn)證邏輯,可能包含加密驗(yàn)證
return this.password.equals(encryptPassword(inputPassword));
}
private String encryptPassword(String password) {
// 加密邏輯
return DigestUtils.md5DigestAsHex(password.getBytes());
}
// getter/setter...
}
PO vs DO 關(guān)鍵區(qū)別:
- PO是"貧血模型":只有數(shù)據(jù),沒(méi)有行為
- DO是"充血模型":既有數(shù)據(jù),也有業(yè)務(wù)行為
3. BO(Business Object)業(yè)務(wù)對(duì)象
作用: 由多個(gè)DO或PO組合而成的復(fù)合對(duì)象,用于完成特定的業(yè)務(wù)場(chǎng)景。
特點(diǎn):
- 由多個(gè)實(shí)體組合而成(聚合根)
- 代表一個(gè)完整的業(yè)務(wù)概念
- 在Service層使用
示例代碼:
// 業(yè)務(wù)對(duì)象 - 組合多個(gè)實(shí)體完成業(yè)務(wù)場(chǎng)景
public class OrderBO {
// 組合多個(gè)DO/PO
private OrderDO order; // 訂單信息
private List<OrderItemDO> items; // 訂單項(xiàng)列表
private UserDO user; // 用戶(hù)信息
private AddressDO address; // 收貨地址
// 業(yè)務(wù)方法
public BigDecimal calculateTotalAmount() {
BigDecimal total = BigDecimal.ZERO;
for (OrderItemDO item : items) {
total = total.add(item.getPrice().multiply(new BigDecimal(item.getQuantity())));
}
// 可能還有優(yōu)惠券、運(yùn)費(fèi)等計(jì)算
return total;
}
public boolean isAvailable() {
return order.getStatus() == 1 && user.isActive();
}
public void applyCoupon(CouponDO coupon) {
// 應(yīng)用優(yōu)惠券的業(yè)務(wù)邏輯
if (coupon.isValid() && calculateTotalAmount().compareTo(coupon.getMinAmount()) >= 0) {
// 應(yīng)用優(yōu)惠
}
}
// getter/setter...
}
使用場(chǎng)景: 復(fù)雜的業(yè)務(wù)邏輯處理,需要多個(gè)實(shí)體協(xié)作時(shí)。
4. DTO(Data Transfer Object)數(shù)據(jù)傳輸對(duì)象
作用: 用于進(jìn)程間數(shù)據(jù)傳輸,比如Service層與Controller層之間,或者微服務(wù)之間。
特點(diǎn):
- 扁平化數(shù)據(jù)結(jié)構(gòu),通常沒(méi)有業(yè)務(wù)邏輯
- 可以根據(jù)需要組合、裁剪字段
- 關(guān)注數(shù)據(jù)傳輸?shù)男屎桶踩?/li>
示例代碼:
// 用于Service層返回給Controller層的數(shù)據(jù)
public class UserDTO {
private Long id;
private String username;
private String email;
private String statusDesc; // 狀態(tài)描述(非數(shù)據(jù)庫(kù)字段)
private Date createTime;
// 通常只有g(shù)etter/setter,沒(méi)有業(yè)務(wù)邏輯
// getter/setter...
// 轉(zhuǎn)換方法(可選)
public static UserDTO fromDO(UserDO userDO) {
if (userDO == null) return null;
UserDTO dto = new UserDTO();
dto.setId(userDO.getId());
dto.setUsername(userDO.getUsername());
dto.setEmail(userDO.getEmail());
dto.setCreateTime(userDO.getCreateTime());
// 狀態(tài)碼轉(zhuǎn)描述
dto.setStatusDesc(userDO.getStatus() == 1 ? "正常" : "禁用");
return dto;
}
}
5. VO(Value Object / View Object)值對(duì)象/視圖對(duì)象
作用: 專(zhuān)門(mén)用于前端展示,根據(jù)界面需求定制數(shù)據(jù)結(jié)構(gòu)。
特點(diǎn):
- 高度定制化,完全為前端服務(wù)
- 可能包含多個(gè)實(shí)體的字段組合
- 字段類(lèi)型可能轉(zhuǎn)換為前端需要的格式(如日期格式化為字符串)
示例代碼:
// 專(zhuān)門(mén)為前端頁(yè)面定制的對(duì)象
public class UserVO {
private Long userId; // 前端需要的字段名
private String userName; // 前端需要的字段名
private String userEmail;
private String createTime; // 格式化的字符串,非Date類(lèi)型
private String lastLoginTime; // 可能來(lái)自其他表的數(shù)據(jù)
private Integer orderCount; // 聚合數(shù)據(jù)
// 可能包含前端需要的特定字段
private Boolean canEdit;
private String avatarUrl;
// 轉(zhuǎn)換方法
public static UserVO fromDTO(UserDTO userDTO, UserStatsDTO stats) {
UserVO vo = new UserVO();
vo.setUserId(userDTO.getId());
vo.setUserName(userDTO.getUsername());
vo.setUserEmail(userDTO.getEmail());
// 格式化日期
vo.setCreateTime(DateUtil.format(userDTO.getCreateTime(), "yyyy-MM-dd HH:mm:ss"));
// 組合其他數(shù)據(jù)
vo.setOrderCount(stats.getOrderCount());
vo.setLastLoginTime(DateUtil.format(stats.getLastLoginTime(), "yyyy-MM-dd HH:mm:ss"));
// 業(yè)務(wù)邏輯判斷
vo.setCanEdit("正常".equals(userDTO.getStatusDesc()));
return vo;
}
// getter/setter...
}
三、 核心區(qū)別對(duì)比表(重要?。?/h2>
| 對(duì)象類(lèi)型 | 英文全稱(chēng) | 所處層級(jí) | 主要作用 | 是否包含業(yè)務(wù)邏輯 | 示例 |
|---|---|---|---|---|---|
| PO | Persistent Object | 持久層 | 數(shù)據(jù)庫(kù)映射 | ? 否 | UserPO |
| DO | Domain Object | 領(lǐng)域?qū)?/td> | 業(yè)務(wù)實(shí)體 | ? 是 | UserDO |
| BO | Business Object | 業(yè)務(wù)層 | 業(yè)務(wù)組合 | ? 是 | OrderBO |
| DTO | Data Transfer Object | 傳輸層 | 數(shù)據(jù)傳輸 | ? 否 | UserDTO |
| VO | View Object | 展示層 | 前端展示 | ? 否 | UserVO |
四、 完整數(shù)據(jù)流轉(zhuǎn)實(shí)戰(zhàn)(重點(diǎn)理解?。?/h2>
讓我們通過(guò)一個(gè)"用戶(hù)訂單詳情"API來(lái)看各層對(duì)象如何協(xié)作:
1. 數(shù)據(jù)庫(kù)層(PO)
// 數(shù)據(jù)庫(kù)表對(duì)應(yīng)的PO
public class UserPO { /* 同上 */ }
public class OrderPO { /* 訂單表映射 */ }
public class OrderItemPO { /* 訂單項(xiàng)表映射 */ }
public class AddressPO { /* 地址表映射 */ }
2. 數(shù)據(jù)訪(fǎng)問(wèn)層(DAO)
@Repository
public class OrderDao {
public OrderPO findById(Long orderId) {
// 使用MyBatis/JPA查詢(xún)數(shù)據(jù)庫(kù),返回PO
return orderMapper.selectById(orderId);
}
}
3. 領(lǐng)域?qū)樱―O)
// 各個(gè)領(lǐng)域?qū)ο蟀约旱臉I(yè)務(wù)邏輯
public class UserDO { /* 包含用戶(hù)相關(guān)業(yè)務(wù)方法 */ }
public class OrderDO { /* 包含訂單相關(guān)業(yè)務(wù)方法 */ }
4. 業(yè)務(wù)層(BO + Service)
@Service
public class OrderService {
public OrderBO getOrderDetail(Long orderId) {
// 1. 查詢(xún)多個(gè)PO
OrderPO orderPO = orderDao.findById(orderId);
List<OrderItemPO> itemPOs = orderItemDao.findByOrderId(orderId);
UserPO userPO = userDao.findById(orderPO.getUserId());
// 2. PO轉(zhuǎn)DO(可能包含業(yè)務(wù)邏輯初始化)
OrderDO orderDO = convertToDO(orderPO);
UserDO userDO = convertToDO(userPO);
// 3. 創(chuàng)建BO(業(yè)務(wù)對(duì)象)
OrderBO orderBO = new OrderBO();
orderBO.setOrder(orderDO);
orderBO.setUser(userDO);
orderBO.setItems(convertToDOs(itemPOs));
// 4. 執(zhí)行業(yè)務(wù)邏輯
if (!orderBO.isAvailable()) {
throw new BusinessException("訂單不可用");
}
return orderBO;
}
}
5. 控制層(DTO + Controller)
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/orders/{orderId}")
public Result<OrderDTO> getOrderDetail(@PathVariable Long orderId) {
// 1. 調(diào)用Service獲取BO
OrderBO orderBO = orderService.getOrderDetail(orderId);
// 2. BO轉(zhuǎn)DTO(數(shù)據(jù)傳輸對(duì)象)
OrderDTO orderDTO = convertBOToDTO(orderBO);
return Result.success(orderDTO);
}
private OrderDTO convertBOToDTO(OrderBO bo) {
OrderDTO dto = new OrderDTO();
// 拷貝基礎(chǔ)字段
BeanUtil.copyProperties(bo.getOrder(), dto);
// 計(jì)算展示字段
dto.setTotalAmount(bo.calculateTotalAmount());
dto.setItemCount(bo.getItems().size());
return dto;
}
}
6. 前端展示層(VO)
// 前端需要的特定數(shù)據(jù)結(jié)構(gòu)
public class OrderVO {
private String orderNumber; // 格式化訂單號(hào)
private String customerName; // 客戶(hù)姓名
private String totalAmount; // 格式化的金額:"¥199.00"
private List<OrderItemVO> items; // 定制化的訂單項(xiàng)
private String statusText; // 狀態(tài)文本
private String createTime; // 格式化時(shí)間
// ... 其他前端特定字段
}
@RestController
public class OrderController {
@GetMapping("/api/v1/orders/{orderId}")
public Result<OrderVO> getOrderForFrontend(@PathVariable Long orderId) {
// 1. 獲取DTO
OrderDTO orderDTO = orderService.getOrderDetail(orderId);
// 2. DTO轉(zhuǎn)VO(為前端定制)
OrderVO orderVO = convertDTOToVO(orderDTO);
return Result.success(orderVO);
}
}
五、 什么情況下可以簡(jiǎn)化?
雖然分層有很多好處,但也不是所有項(xiàng)目都需要這么復(fù)雜:
適合完整分層的情況:
- 大型項(xiàng)目:團(tuán)隊(duì)規(guī)模較大,需要明確分工
- 復(fù)雜業(yè)務(wù):業(yè)務(wù)邏輯復(fù)雜,需要清晰的架構(gòu)
- 長(zhǎng)期維護(hù):項(xiàng)目需要長(zhǎng)期迭代和維護(hù)
- 微服務(wù)架構(gòu):服務(wù)間需要明確的數(shù)據(jù)契約
可以簡(jiǎn)化的情況:
- 小型項(xiàng)目:個(gè)人項(xiàng)目或小型團(tuán)隊(duì)
- 簡(jiǎn)單CRUD:沒(méi)有復(fù)雜業(yè)務(wù)邏輯
- 快速原型:需要快速驗(yàn)證想法
簡(jiǎn)化方案:
// 簡(jiǎn)單項(xiàng)目可以PO、DO合一
@Entity
public class User {
@Id
private Long id;
private String username;
// 也可以包含簡(jiǎn)單業(yè)務(wù)方法
public boolean isActive() {
return status == 1;
}
}
// 甚至可以PO、DTO、VO合一(不推薦用于正式項(xiàng)目)
六、 最佳實(shí)踐總結(jié)
- 明確各層職責(zé):每層只處理自己該處理的數(shù)據(jù)
- 使用工具類(lèi)轉(zhuǎn)換:用BeanUtil、MapStruct等工具簡(jiǎn)化對(duì)象轉(zhuǎn)換
- 避免過(guò)度設(shè)計(jì):根據(jù)項(xiàng)目復(fù)雜度選擇合適的分層方案
- 保持命名規(guī)范:使用PO、DTO、VO等后綴,提高代碼可讀性
- 文檔化數(shù)據(jù)流:在團(tuán)隊(duì)中明確各層對(duì)象的轉(zhuǎn)換關(guān)系
七、 常見(jiàn)問(wèn)題解答(FAQ)
Q1:DO和PO一定要分開(kāi)嗎?
A:在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)中建議分開(kāi),簡(jiǎn)單CRUD項(xiàng)目可以合并。
Q2:DTO和VO有什么區(qū)別?
A:DTO關(guān)注數(shù)據(jù)傳輸(后端內(nèi)部),VO關(guān)注前端展示。在前后端分離架構(gòu)中,VO就是給前端用的API響應(yīng)對(duì)象。
Q3:什么時(shí)候用BO?
A:當(dāng)需要多個(gè)實(shí)體協(xié)作完成一個(gè)業(yè)務(wù)場(chǎng)景時(shí)使用BO。
Q4:這些對(duì)象轉(zhuǎn)換會(huì)不會(huì)影響性能?
A:會(huì)有輕微影響,但對(duì)于大多數(shù)業(yè)務(wù)系統(tǒng)來(lái)說(shuō),可維護(hù)性的收益遠(yuǎn)大于性能損失??梢允褂肕apStruct等高效轉(zhuǎn)換工具。
總結(jié)
到此這篇關(guān)于Java中VO、DTO、BO、DO、PO的文章就介紹到這了,更多相關(guān)Java中VO、DTO、BO、DO、PO內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 深扒Java中POJO、VO、DO、DTO、PO、BO、AO、DAO的概念和區(qū)別以及如何應(yīng)用
- java中PO、VO、BO、POJO、DAO、DTO、TO、QO、Bean、conn的理解
- java中VO和DTO之間的轉(zhuǎn)換實(shí)現(xiàn)
- java實(shí)現(xiàn)相同屬性名稱(chēng)及相似類(lèi)型的pojo、dto、vo等互轉(zhuǎn)操作
- Java后端中dto、vo、entity的區(qū)別淺析
- java中VO PO DTO POJO BO DO對(duì)象的應(yīng)用場(chǎng)景及使用方式
- Java中DTO和VO的區(qū)別舉例詳解
相關(guān)文章
由淺到深帶你詳談Java實(shí)現(xiàn)數(shù)組擴(kuò)容的三種方式
這篇文章主要詳細(xì)介紹了Java實(shí)現(xiàn)數(shù)組擴(kuò)容的三種方式,新建一個(gè)數(shù)組,把原來(lái)數(shù)組的內(nèi)容搬到新數(shù)組中,使用system.arraycopy(),使用java.util.Arrays.copyOf()這三種方式,具有一定的參考價(jià)值,需要的朋友可以借鑒一下2023-06-06
springboot使用Validator校驗(yàn)方式
這篇文章主要介紹了springboot使用Validator校驗(yàn)方式,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-01-01
SpringBoot開(kāi)發(fā)實(shí)戰(zhàn)之自動(dòng)配置
SpringBoot的核心就是自動(dòng)配置,自動(dòng)配置又是基于條件判斷來(lái)配置Bean,下面這篇文章主要給大家介紹了關(guān)于SpringBoot開(kāi)發(fā)實(shí)戰(zhàn)之自動(dòng)配置的相關(guān)資料,需要的朋友可以參考下2021-08-08
SpringBoot2.3定制錯(cuò)誤頁(yè)面的方法示例
這篇文章主要介紹了SpringBoot2.3定制錯(cuò)誤頁(yè)面的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
Java 實(shí)現(xiàn)一個(gè)漢諾塔實(shí)戰(zhàn)練習(xí)
漢諾塔是源于印度一個(gè)古老傳說(shuō)的益智玩具。大梵天創(chuàng)造世界時(shí)做了三根石柱,在一根柱子上從下往上按大小順序摞著64片黃金圓盤(pán)。大梵天命令婆羅門(mén)把圓盤(pán)從下面開(kāi)始按大小順序重新擺放在另一根柱子上。并且規(guī)定,在小圓盤(pán)上不能放大圓盤(pán),三根柱子之間一次只能移動(dòng)一個(gè)圓盤(pán)2021-10-10
Java消息隊(duì)列RabbitMQ入門(mén)詳解
這篇文章主要介紹了Java消息隊(duì)列RabbitMQ入門(mén)詳解,RabbitMQ是使用Erlang語(yǔ)言開(kāi)發(fā)的開(kāi)源消息隊(duì)列系統(tǒng),基于AMQP協(xié)議 來(lái)實(shí)現(xiàn),AMQP的主要特征是面向消息、隊(duì)列、路由(包括點(diǎn)對(duì)點(diǎn)和發(fā)布 /訂閱)、可靠性、安全,需要的朋友可以參考下2023-07-07
mybatis多個(gè)區(qū)間處理方式(雙foreach循環(huán))
這篇文章主要介紹了mybatis多個(gè)區(qū)間處理方式(雙foreach循環(huán)),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02

