在很多初级 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:数据边界
写对了项目会变得:
- 清晰
- 稳定
- 可扩展
- 易维护
写错了项目会:
- 混乱
- 难以维护
- 对象到处乱飞
- 改一个字段牵一发动全身