Spring Boot 中为什么不要将 DTO 与 Entity 混合使用
Spring Boot 中为什么不要将 DTO 与 Entity 混合使用
刚开始学习 Spring Boot 的时候,我对 DTOs(数据传输对象)和 Entities(实体类)没有太深入的思考。通常的做法就是创建一个类,然后到处使用——数据库操作、API 接口、业务服务等等。看起来一切都很顺利……直到问题出现。
在这篇文章中,我想分享一下关于 DTOs 和 Entities 的一些经验教训。我会解释它们各自的作用,为什么需要将它们分离,以及忽略这一点会带来哪些实际问题。
最初的错误做法
在某个项目的初期,我创建了一个 User
类,包含 id
、name
、email
和 password
等字段。然后在以下所有场景中都使用这同一个类:
- 数据库持久化(作为 JPA 实体)
- 接收前端传递的数据
- 返回 API 响应结果
开发速度确实很快,代码量也不多。但随着项目的发展,问题逐渐暴露出来。
问题的出现
有一天,产品经理提出了新需求:前端需要展示用户的公开资料,但不能包含邮箱和密码信息。
听起来很简单,对吧?
但问题是,我的 API 直接返回完整的 User
实体对象,响应中包含了敏感数据,比如密码哈希值。
我们只能临时用 @JsonIgnore
注解来解决,但接下来另一个确实需要返回邮箱的 API 又出现了问题。
这时候才意识到,一个类已经无法满足所有需求了。
DTO 的解决方案
我创建了一个新的 UserDTO
类,只包含需要在响应中返回的字段——比如 id
、name
,可能还有 profilePicture
。
现在可以精确控制哪些数据可以进入或离开系统。
示例代码:
public class UserDTO {
private Long id;
private String name;
private String profilePicture;
}
数据映射的方式:
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setName(user.getName());
dto.setProfilePicture(user.getProfilePicture());
后来,我开始使用 MapStruct 来自动化这个映射过程,不过那是后话了。
为什么混合使用 DTO 和 Entity 是个坏习惯
以下是我在混合使用 DTOs 和 Entities 时遇到的实际问题:
- 在 API 接口中意外暴露敏感数据(如密码或内部 ID)
- 修改某个字段时影响多个 API 接口
- 数据库重构时难以避免对前端的影响
- 接受了不应该直接保存到数据库的用户输入
另一个案例:输入 DTO vs. Entity
在处理 API 输入时我也遇到了类似问题。我有一个实体类包含 createdAt
、isAdmin
和 status
等字段,但我不希望用户能够为这些字段传值。
由于在 API 请求中直接使用了实体类,结果有人发送了一个包含 "isAdmin": true
的 POST
请求,而系统竟然真的生效了!
从那时起,我为用户创建操作单独创建了一个输入 DTO。它只包含我们期望用户提供的字段:name
、email
、password
。其他字段都在后端自动设置。
血的教训
- Entities 专门用于数据库操作
- DTOs 专门用于 API 接口和业务逻辑
- 不要混合使用,即使看起来更简单
- 根据需要为输入和输出创建不同的 DTOs
- 如果不想手写映射代码,可以使用 MapStruct 或 ModelMapper 等工具
刚开始的时候,创建单独的 DTO 类确实感觉像是额外的工作量。但后来发现,它为我节省了大量的调试时间,并且让我对应用程序的行为有了更好的控制。
总结
刚入门的时候,没有人告诉我要分离 DTOs 和 Entities。我只能通过踩坑来学习——各种 bug、奇怪的行为、接口异常等等。
如果你正在开发 Spring Boot 应用,建议尽早创建专门的 DTOs。相信我,未来的你会感谢现在的决定。
你在混合使用 DTOs 和 Entities 时遇到过类似问题吗?欢迎在评论区分享你的经验。