简单来说,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服务的重要最佳实践。