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:数据传输对象

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

dart 复制代码
@Data
publicclassUserCreateDTO{
    @NotBlank
    private String username;

    @NotBlank
    private String password;

    private String phone;
}

特点:

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

2)Entity:数据库实体类

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

dart 复制代码
@Data
@TableName("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 代码 可测、可读、可复用。

dart 复制代码
@Data
publicclassUserBO{
    private Long id;
    private String username;
    private String phone;

    // 业务特有字段
    privateboolean newUser;
}

特点:

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

4)VO:前端视图对象(最终展示)

职责:决定前端看见什么。 业务字段需做处理:脱敏、格式化、组合字段等。

dart 复制代码
@Data
publicclassUserVO{
    private Long id;
    private String username;

    // 脱敏手机号
    private String phoneMasked;
}

特点:

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

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

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

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

示例:

dart 复制代码
@Mapper(componentModel = "spring")
publicinterfaceUserConverter{

    UserBO dtoToBO(UserCreateDTO dto);

    UserEntity boToEntity(UserBO bo);

    UserVO boToVO(UserBO bo);
}

使用:

dart 复制代码
@Autowired
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、完整的分层调用链(可直接用于项目模板)

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

控制器中只写:

dart 复制代码
@PostMapping("/create")
public Result<UserVO> create(@RequestBody @Valid 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:数据边界

写对了项目会变得:

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

写错了项目会:

  • 混乱
  • 难以维护
  • 对象到处乱飞
  • 改一个字段牵一发动全身
相关推荐
l***91471 小时前
Plugin ‘org.springframework.bootspring-boot-maven-plugin‘ not found的解决方法
java·maven
u***B7921 小时前
Spring Boot实时推送技术详解:三个经典案例
spring boot·后端·状态模式
t***82111 小时前
Spring Boot 整合 MyBatis 与 PostgreSQL 实战指南
spring boot·postgresql·mybatis
n***F8751 小时前
Spring Boot + Spring AI快速体验
人工智能·spring boot·spring
U***l8321 小时前
Spring Boot 整合 Redis 步骤详解
spring boot·redis·bootstrap
W***r261 小时前
Tomcat10下载安装教程
java
霸道流氓气质1 小时前
SpringBoot添加JSP支持
java·spring boot·后端
guslegend1 小时前
第1章:Mysql数据库架构演变历史
java
烤麻辣烫1 小时前
黑马程序员苍穹外卖(新手)DAY10
java·开发语言·学习·spring·intellij-idea