簡單來說,Entity
?和?DTO
?代表了數據在不同層次和場景下的不同形態和目的。
它們最根本的區別在于:職責和目的不同。
一句話概括
Entity:代表數據庫中的表,是業務邏輯的核心,與持久化(數據庫)緊密相關。
DTO:代表傳輸的數據,是API交互的契約,與客戶端(前端、其他服務)緊密相關。
詳細對比
- DTO(Data Transfer Object): 數據傳輸對象,這個概念來源于J2EE的設計模式,原來的目的是為了EJB的分布式應用提供粗粒度的數據實體,以減少分布式調用的次數,從而提高分布式調用的性能和降低網絡負載。(個人理解:原先需要獲取用戶信息和用戶訂單需要調用倆個接口,將返回數據整合為一個DTO,調用一次就可以獲取所有數據)
- Entity: 實體類,一般與持久層(通常是關系型數據庫)的數據結構形成一一對應的映射關系,如果持久層是關系型數據庫,那么,數據表中的每個字段(或若干個)就對應Entity的一個(或若干個)屬性
為了更清晰地理解,我們從多個維度進行對比:
特性維度 | Entity (實體) | DTO (數據傳輸對象) |
---|---|---|
核心職責 | 模型化業務領域,承載核心業務邏輯和規則。 | 傳輸數據,在不同進程或網絡間安全、高效地搬運數據。 |
所屬層級 | 領域層 / 數據持久層。與數據庫ORM(如Hibernate, MyBatis)直接交互。 | 表現層 / 應用層。用于Controller的入參和出參。 |
與數據庫關系 | 強關聯。其字段通常與數據庫表結構一一對應,包含主鍵、外鍵、關聯關系等。 | 無關聯。完全不知道數據庫的存在,結構根據API需求定制。 |
設計重點 | 準確性和完整性。要完整描述業務對象,并包含數據完整性約束(如@NotNull )。 | API契約和性能。要明確、穩定,并且只包含客戶端需要的數據,避免不必要的字段傳輸以提升性能。 |
典型特征 | ? 包含ORM注解(如@Entity ,?@Table ,?@Id )? 包含業務邏輯方法 ? 可能有關聯對象的集合(如 List<Order> ) | ? 通常是純數據容器(只有getter/setter) ? 包含驗證注解(如 @Email ,用于接口參數校驗)? 結構扁平化,可能組合多個Entity的數據 |
變化原因 | 數據庫結構或業務邏輯變化。 | 客戶端界面或需求變化。 |
舉例說明
假設我們有一個博客系統,用戶(User
)和文章(Article
)是一對多關系。
1. Entity (數據庫模型)
@Entity
@Table(name = "user")
public class UserEntity {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(nullable = false, unique = true)private String username;@Column(nullable = false)private String password; // 敏感信息,加密存儲@Column(nullable = false, unique = true)private String email;@OneToMany(mappedBy = "author")private List<ArticleEntity> articles; // 關聯對象// 業務邏輯方法public boolean isPasswordValid(String rawPassword) {// ... 密碼驗證邏輯return true;}// Getter和Setter省略...
}
注意:這個類直接映射數據庫,包含敏感字段
password
和整個ArticleEntity
集合。絕對不應該直接返回給前端。
2. DTO (API傳輸模型)
場景一:獲取用戶基本信息(用于用戶列表)
public class UserSimpleDto {private Long id;private String username;// 沒有 password, email, articles// Getter和Setter...
}
場景二:用戶注冊時提交的數據
public class UserCreationDto {@NotBlank(message = "用戶名不能為空")@Size(min = 3, max = 16)private String username;@NotBlank(message = "密碼不能為空")@Size(min = 6, max = 20)private String password;@Email(message = "郵箱格式不正確")private String email;// 沒有 id// Getter和Setter...
}
場景三:獲取用戶詳情(包含部分關聯信息)
public class UserDetailDto {private Long id;private String username;private String email;// 不是返回完整的ArticleEntity,而是專門為API設計的ArticleDtoprivate List<ArticlePreviewDto> articles;// Getter和Setter...
}public class ArticlePreviewDto {private Long id;private String title;private LocalDateTime createdAt;// 沒有文章內容等詳細字段,因為是“預覽”
}
為什么不能直接用Entity作為接口的輸入輸出?
安全問題:
Entity
通常包含敏感信息(如password
、salary
等),直接返回會給系統帶來巨大安全風險。性能問題:
Entity
的關聯關系(如articles
)可能導致ORM框架加載大量不需要的關聯數據(N+1查詢問題),造成性能瓶頸。而DTO可以精確控制返回的數據。API穩定性:數據庫模型(Entity)的變化會直接導致API的變化,從而迫使所有客戶端升級。而DTO在Entity和客戶端之間提供了一個緩沖層,你可以修改Entity結構而不影響API契約,只需調整Entity到DTO的轉換邏輯即可。
過度傳輸或傳輸不足:
Entity
是為了完整描述業務對象,而前端可能只需要其中幾個字段(過度傳輸),或者需要組合多個Entity的字段(傳輸不足)。DTO可以完美解決這兩種情況。驗證分離:對API參數的驗證(如
@Email
)應該放在DTO上,而不是Entity上。Entity的驗證更關注數據完整性(如@NotNull
)。
工作流程
一個標準的后端處理流程如下:
入參:客戶端發送請求 ->?Controller接收并解析JSON到DTO?-> 進行參數校驗 -> 將DTO轉換為Entity?-> 調用Service。
處理:Service使用Entity進行業務邏輯處理,并調用Repository保存到數據庫。
出參:Service獲取處理后的Entity?-> 轉換為DTO?->?Controller將DTO返回給客戶端(轉換為JSON)。
其中的轉換過程通常使用工具類如?MapStruct、ModelMapper?或手動編寫的轉換器來完成。
總結
Entity | DTO | |
---|---|---|
問自己 | 它如何在數據庫中存儲? | 客戶端需要看到什么? |
目的 | 建模業務,與數據庫交互 | 網絡傳輸,定義API契約 |
關鍵 | 不要將Entity直接暴露給外部。 | 總是通過DTO來與客戶端交互。 |
將Entity和DTO分離是構建健壯、可維護、安全且高性能的Web服務的重要最佳實踐。