目錄
🧩 一、數據流走向(總覽)
⚙️ 二、DTO → Entity:在業務層中完成
⚙️ 三、Entity → VO:返回時的轉換
🧠 四、關鍵點總結
✨ 五、推薦實踐(讓代碼更乾淨)
1. 使用工具類簡化拷貝
2. 把轉換邏輯封裝成“轉換器類”
✅ 六、一句話總結
在現代 Spring Boot Web 開發中,為了實現前後端分離、保證數據安全和明確分層,我們通常會使用三種類型的 Java 對象來處理數據:DTO、Entity 和 VO。
這個模式的核心思想是:在不同的應用層之間,使用專門的對象進行數據傳輸。
🧩 一、數據流走向(總覽)
下面是-個標準 Web 請求的數據流轉過程:
前端 JSON 請求體
↓
DTO(數據傳輸對象) ← 【控制器層 Controller】(用於接收輸入)
↓
Entity(實體對象) ← 【業務層 Service】(轉換為 Entity,用於保存到數據庫)
↓
數據庫
↓
Entity(實體對象) ← 【業務層 Service】(從數據庫取出)
↓
VO(視圖對象) ← 【控制器層 Controller/Service】(轉換為 VO,用於封裝返回前端)
↓
前端 JSON 響應體
⚙️ 二、DTO → Entity:在業務層中完成
DTO (Data Transfer Object):數據傳輸對象。它的唯一職責是接收前端傳來的數據。
當 Controller 接收到前端的 JSON 請求時,Spring MVC 會自動將其封裝為 DTO 對象。
// Controller
@PostMapping("/register")
public ApiResponse<Long> register(@Valid @RequestBody UserRegisterRequest dto) {
// 此時,dto 對象就是前端傳來的 JSON 數據
// 然後我們把 dto 傳給業務層
Long userId = userService.register(dto);
return ApiResponse.success("註冊成功", userId);
}
Entity (實體):數據庫實體對象。它嚴格對應數據庫中的表結構。
這一步轉換髮生在 Service (業務) 層。Service 層負責核心業務邏輯,它接收 DTO,然後將其轉換為 Entity,並補充業務所需的其他字段(比如默認權限、初始積分、加密密碼等)。
// ServiceImpl
public Long register(UserRegisterRequest dto) {
// 1. 手動將 DTO 字段拷貝給 Entity
User user = new User();
user.setUsername(dto.getUsername());
// 業務邏輯:密碼需要加密
user.setPassword(passwordEncoder.encode(dto.getPassword()));
// 業務邏輯:暱稱默認為用户名
user.setNickname(StringUtils.hasText(dto.getNickname()) ? dto.getNickname() : dto.getUsername());
user.setPhone(dto.getPhone());
user.setEmail(dto.getEmail());
// 2. 也可以用工具類來簡化拷貝
// BeanUtils.copyProperties(dto, user);
// (注意:使用工具類後,仍需手動處理密碼加密等特殊邏輯)
// 3. 補充前端不傳的業務字段
user.setRole("user");
user.setStatus(1);
user.setCreditScore(100);
// 4. 通過 MyBatis-Plus 將 Entity 存入數據庫
save(user);
return user.getId();
}
✅ 這一步的作用:
- 把前端的請求參數(DTO)轉換成數據庫對應的實體對象(Entity)。
- 在轉換過程中執行業務邏輯(如加密、設置默認值)。
⚙️ 三、Entity → VO:返回時的轉換
VO (View Object):視圖對象。它專門用於封裝後端需要返回給前端的數據。
我們通常不會直接把 Entity 返回給前端,因為 Entity 裏可能包含敏感字段(比如 password、salt)或者前端不需要的字段(比如 is_deleted、update_time)。
VO 的作用就是隻挑選前端需要的字段。
示例:
// 專門用於前端展示的 UserVO
public class UserVO {
private Long id;
private String username;
private String nickname;
private String avatar;
private String email;
}
轉換可以在 Service 層完成,也可以在 Controller 層完成(推薦在 Service 層)。
// ServiceImpl 中...
// 假設我們從數據庫查到了 user (Entity)
User user = getById(userId);
// 1. 轉換為 VO
UserVO vo = new UserVO();
vo.setId(user.getId());
vo.setUsername(user.getUsername());
vo.setNickname(user.getNickname());
vo.setAvatar(user.getAvatar());
vo.setEmail(user.getEmail());
// 2. 或者同樣用工具類
// BeanUtils.copyProperties(user, vo);
// 3. 在 Controller 返回 VO
return ApiResponse.success("查詢成功", vo);
前端最終拿到的 JSON 就會非常乾淨:
{
"success": true,
"message": "查詢成功",
"data": {
"id": 1001,
"username": "tom",
"nickname": "Tom",
"avatar": "avatar/default.png",
"email": "tom@demo.com"
}
}
🧠 四、關鍵點總結
|
轉換方向
|
發生位置
|
意義
|
|
DTO → Entity |
在 Service 層 |
把前端請求數據轉換為數據庫對象(用於保存) |
|
Entity → VO |
在 Service 或 Controller 層 |
把數據庫對象轉換為前端可展示的數據(用於返回) |
✨ 五、推薦實踐(讓代碼更乾淨)
1. 使用工具類簡化拷貝
手動 set 和 get 非常繁瑣且容易出錯。
- Spring 自帶:
BeanUtils.copyProperties(source, target); - 常用增強庫:
- MapStruct(推薦):在編譯時自動生成類型安全的轉換代碼,性能極高。
- ModelMapper:在運行時通過反射自動映射,非常靈活但性能略慢。
2. 把轉換邏輯封裝成“轉換器類”
當轉換邏輯變多時,可以創建一個專門的 Convert 類(或接口),讓 Service 層保持乾淨。
public class UserConvert {
// 使用 MapStruct 的示例
// @Mapper(componentModel = "spring")
// public interface UserConvert {
// UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
// User toEntity(UserRegisterRequest dto);
// UserVO toVO(User user);
// }
// 手動封裝的示例
public static User toEntity(UserRegisterRequest dto) {
User user = new User();
BeanUtils.copyProperties(dto, user);
// ... 其他特殊處理
return user;
}
public static UserVO toVO(User user) {
UserVO vo = new UserVO();
BeanUtils.copyProperties(user, vo);
return vo;
}
}
然後你的 Service 代碼就可以簡化為:
// ServiceImpl
User user = UserConvert.toEntity(request);
// ... 處理加密等 ...
save(user);
return UserConvert.toVO(user);
✅ 六、一句話總結
|
環節
|
作用
|
代碼位置
|
|
DTO |
接收前端輸入 |
Controller 入參 |
|
Entity |
持久化到數據庫 |
Service + Mapper |
|
VO |
返回前端展示 |
Controller 出參 |
|
DTO→Entity |
在 Service 中轉換 |
業務邏輯處理時 |
|
Entity→VO |
在 Service 或 Controller 中轉換 |
返回結果時 |