实用指南:Spring Boot:DTO、VO、BO、Entity 的正确工程化分层

在很多初级 Spring Boot 项目里,你经常会看到这样的写法:

  • Controller 接口直接用 Entity 做入参

  • Service 返回 Entity 给前端

  • 数据库字段一改,整个项目跟着震荡

  • 新人接手完全不知道 什么该在哪一层做

这种“全都写在一个类里”的开发方式,小项目还能凑合,一旦遇到中大型业务,分分钟失控。

企业级项目讲究的是 边界清晰、模型稳定、可扩展强。 而实现这些的基础设施,就是 DTO、VO、BO、Entity 的分层模型。

1、为什么必须分层?

可以用一句工程化黄金法则解释:“内部结构可以变,但外部接口必须稳定。”

DTO、VO、BO、Entity 的职责就是把“内部变化”隔离开,

让修改成本缩到最小:

  • 数据库变 → 仅修改 Entity
  • 业务字段变 → 修改 BO
  • API 入参变化 → 只影响 DTO
  • 返回字段调整 → 修改 VO,不影响内部逻辑

这样你就不会因为一个字段改了导致全链路爆炸。

2、四层模型的职责与关系

用一句话总结每个对象:

  • DTO:入参传输对象,接收外部传来的内容(Request)
  • VO:展示对象,返回给前端(Response)
  • BO:业务对象,Service 层的内部处理载体
  • Entity:数据库实体,与表结构一一对应

关系图:
在这里插入图片描述
它们不是四层孤立结构,而是纵向贯穿整个业务链路的模型体系。

3、四层模型的详细解释 + 完整代码示例

下面用一个“用户管理”模块来讲透。

1)DTO:数据传输对象
职责:只负责接收外部输入。 不要放业务逻辑、不要放数据库字段,越精简越安全。


publicclassUserCreateDTO{

private String username;

private String password;
private String phone;
}

特点:

  • 一定要加字段校验(例如 @NotBlank)
  • 不允许与数据库结构耦合
  • 只负责“接收请求参数”

2)Entity:数据库实体类
职责:与 数据库表结构完全一致。 即便表字段很丑,也不能随便删。


("t_user")
publicclassUserEntity{
private Long id;
private String username;
private String password;
private String phone;
private Date createTime;
private Date updateTime;
}

特点:

  • 仅用于“数据库映射”
  • 表结构变更 → 只改这个类
  • 不要在 Controller、VO、DTO 里使用它

3)BO:业务对象(Service 层的“大脑”)
很多项目忽略 BO,这是最致命的! BO 是业务逻辑的载体,让 Service 代码 可测、可读、可复用。


publicclassUserBO{
private Long id;
private String username;
private String phone;
// 业务特有字段
privateboolean newUser;
}

特点:

  • 包含业务逻辑需要的额外数据(但不存数据库)
  • 是 Entity 的升级版
  • 比 Entity 更干净,比 VO 更业务
  • 没有 BO 的项目,就像没有脑子的业务逻辑。

4)VO:前端视图对象(最终展示)
职责:决定前端看见什么。 业务字段需做处理:脱敏、格式化、组合字段等。


publicclassUserVO{
private Long id;
private String username;
// 脱敏手机号
private String phoneMasked;
}

特点:

  • 不返回原始敏感字段
  • 专门给前端展示
  • 可以组合多个业务来源

4、模型之间如何转换?(最关键的工程化规范)

为了避免 MVC 层之间乱传对象,转换必须统一。

1)推荐使用 MapStruct(高性能编译期转换)
示例:

(componentModel = "spring")
publicinterfaceUserConverter{
UserBO dtoToBO(UserCreateDTO dto);
UserEntity boToEntity(UserBO bo);
UserVO boToVO(UserBO bo);
}

使用:


private UserConverter converter;
public UserVO createUser(UserCreateDTO dto){
UserBO bo = converter.dtoToBO(dto);
// 业务处理
bo.setNewUser(true);
// 入库
UserEntity entity = converter.boToEntity(bo);
userMapper.insert(entity);
// 返回前端
return converter.boToVO(bo);
}

好处:

  • 不需要手写 set/get(避免调试地狱)
  • 性能比 BeanUtils 高很多
  • 规范清晰

5、完整的分层调用链(可直接用于项目模板)

Controller
->接收DTO
->调用Service
Service
->DTO->BO
->业务处理(核心逻辑)
->BO->Entity
->持久化
->返回BO→VO
Repository
->操作Entity→DB

控制器中只写:

("/create")
public Result<UserVO> create(  UserCreateDTO dto){
  return Result.success(userService.createUser(dto));
  }

6、常见错误示范(请务必避免)

❌ 1. 用 Entity 做入参
public Result addUser(@RequestBody UserEntity entity)
风险:表字段一变,整个接口崩!

❌ 2. Service 返回 Entity 给前端
容易把密码、状态字段直接暴露。

❌ 3. 没有 BO,复杂业务揉到 Service 里
代码大面积重复,风险极高。

❌ 4. 不做字段脱敏
手机号、身份证、邮箱暴露给前端,等着挨打。

总结

DTO、VO、BO、Entity 有着严格的职责:

  • DTO:输入边界
  • VO:输出边界
  • BO:业务边界
  • Entity:数据边界

写对了项目会变得:

  • 清晰
  • 稳定
  • 可扩展
  • 易维护

写错了项目会:

  • 混乱
  • 难以维护
  • 对象到处乱飞
  • 改一个字段牵一发动全身
posted @ 2025-12-26 21:20  gccbuaa  阅读(0)  评论(0)    收藏  举报