目錄

🧩 一、數據流走向(總覽)

⚙️ 二、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();
}

這一步的作用

  1. 把前端的請求參數(DTO)轉換成數據庫對應的實體對象(Entity)。
  2. 在轉換過程中執行業務邏輯(如加密、設置默認值)。

⚙️ 三、Entity → VO:返回時的轉換

VO (View Object):視圖對象。它專門用於封裝後端需要返回給前端的數據

我們通常不會直接把 Entity 返回給前端,因為 Entity 裏可能包含敏感字段(比如 passwordsalt)或者前端不需要的字段(比如 is_deletedupdate_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

ServiceController

把數據庫對象轉換為前端可展示的數據(用於返回

✨ 五、推薦實踐(讓代碼更乾淨)

1. 使用工具類簡化拷貝

手動 setget 非常繁瑣且容易出錯。

  • 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 中轉換

返回結果時