分層架構中數據表示物件的邊界劃分直接決定系統耦合度與可維護性。本文針對 Struts + Hibernate 體系,深度剖析 Form Bean、Value Object (VO)、Persistent Object (PO)、Data Transfer Object (DTO) 的職責邊界、轉換責任歸屬、常見反模式與現代演進方案。
| 物件類型 | 別名 | 所在層 | 核心職責 | 生命週期 |
|---|---|---|---|---|
| Form Bean | ActionForm | Web/表現層 | HTTP 參數綁定、校驗、頁面回顯 | 請求級 |
| VO (Value Object) | Domain Object / BO | 業務/服務層 | 承載領域邏輯、業務規則、行為豐富 | 事務/用例級 |
| PO (Persistent Object) | Entity | 持久/數據層 | ORM 映射、資料庫表對應、狀態管理 | Session/事務級 |
| DTO | Data Transfer Object | 跨層/遠程傳輸 | 序列化傳輸、API 契約、扁平化數據 | 請求/響應級 |
┌─────────────────────────────────────────────────────────────┐
│ Web Layer (JSP/Controller) │
│ Form Bean ←→ Controller ←→ Service Interface │
│ ↑ ↑ │
│ 轉換責任 調用邊界 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Service Layer (Business Logic) │
│ VO/Domain Object ←→ Service Impl ←→ DAO/Repository │
│ ↑ ↑ │
│ 領域邏輯 轉換責任 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Persistence Layer (DAO/ORM) │
│ PO/Entity ←→ Hibernate Session / JPA EntityManager │
└─────────────────────────────────────────────────────────────┘
| 違規模式 | 代碼特徵 | 危害 |
|---|---|---|
| PO 滲透到 Web 層 | `request.setAttribute("user", hibernateUser)` → JSP 直接 `${user.orders.size}` | LazyInitializationException、N+1 查詢、視圖耦合領域模型、安全風險 (敏感字段暴露) |
| Form Bean 滲透到 Service 層 | `service.register(userForm)` → Service 內部 `userForm.getPassword()` | 服務層依賴 Web 框架、難以復用 (非 Web 客戶端)、校驗邏輯分散 |
| VO/PO 混用 | Hibernate Entity 直接當 VO 用,添加業務方法 | 持久化關注點污染領域模型、Session 管理複雜、測試困難 |
| 單一「萬能」對象 | 一個類同時作 Form/VO/PO/DTO | 修改連鎖反應、字段爆炸、職責不清、序列化風險 |
@PostMapping("/register")
public String register(@Valid UserForm form, BindingResult errors) {
if (errors.hasErrors()) return "register";
// 手工映射 (簡單場景)
UserVO vo = new UserVO();
vo.setUsername(form.getUsername());
vo.setEmail(form.getEmail());
vo.setPassword(form.getPassword()); // 明文傳給 Service 加密
// 或使用映射框架 (複雜場景)
// UserVO vo = mapper.map(form, UserVO.class);
userService.register(vo);
return "redirect:success";
}
@Transactional
public void register(UserVO vo) {
// 領域邏輯
UserPO po = new UserPO();
po.setUsername(vo.getUsername());
po.setEmail(vo.getEmail());
po.setPasswordHash(passwordEncoder.encode(vo.getPassword()));
po.setCreatedAt(LocalDateTime.now());
// 關聯處理
RolePO defaultRole = roleRepository.findByCode("USER");
po.getRoles().add(defaultRole);
userRepository.save(po); // Hibernate 持久化
// 返回 VO (不暴露 PO)
return toVO(po);
}
| 框架 | 特點 | 適用場景 |
|---|---|---|
| MapStruct | 編譯期生成、類型安全、高效能、零反射 | 首選:Form↔VO、VO↔DTO、PO↔VO |
| Dozer | 運行期反射、支援複雜映射、XML/註解配置 | 遺留項目、動態映射需求 |
| ModelMapper | 約定優於配置、智能匹配、反射 | 快速原型、字段名高度一致 |
| 手工映射 | 完全控制、零依賴、易調試 | 核心業務、邏輯複雜、安全敏感 |
短期/簡單項目:可接受。Hibernate Entity 脫離 Session 後變為脫管狀態,實質上等同 VO。
風險:
建議:項目啟動即建立明確 PO/VO 分離,使用 MapStruct 映射,長期收益遠超初期投入。
// Controller 接收 DTO (API 契約)
@PostMapping("/api/users")
public ResponseEntity create(@RequestBody @Valid CreateUserDTO req) {
UserVO vo = userService.create(req); // Service 返回 VO
return ResponseEntity.ok(mapper.toDTO(vo)); // 輸出 DTO
}
// 內部 Service 使用 VO (豐富領域模型)
public UserVO create(CreateUserDTO req) {
User user = User.register(req.getUsername(), req.getEmail(), req.getPassword());
userRepository.save(user);
return toVO(user);
}
// 區別:
// CreateUserDTO: 輸入驗證、文檔化、版本化、不含 ID/創建時間
// UserVO: 領域行為、業務規則、狀態機
// UserDTO: 輸出格式、扁平化、隱藏敏感字段、Hypermedia 鏈接
嚴守數據表示邊界,是分層架構低耦合、高內聚、可測試、可演進的基石。