当前位置: 首页 > news >正文

SpringBoot分层概念澄清 DTO、BO、VO

spring boot项目中: DTO就是前端到控制层 BO就是数据库到服务层 VO就是服务层到前端 这样理解是否正确?

在Spring Boot项目中,关于DTO、BO和VO的划分确实存在一些常见约定,但你的理解中有些地方需要进一步澄清。下面是对这些概念在典型分层架构中的解释:

1. DTO(Data Transfer Object,数据传输对象)

  • 你的理解:前端到控制层

  • 更准确的定位:DTO 通常用于不同层之间的数据传输,尤其是控制层(Controller)与服务层(Service)之间,也可以用于服务层与外部接口之间。

  • 作用

    • 接收前端请求参数(如JSON → DTO)

    • 返回给前端的响应数据(如DTO → JSON)

    • 避免直接暴露实体(Entity)结构,同时可以聚合多个字段或进行数据校验

  • 方向双向(前端 → 控制层,控制层 → 前端)

2. BO(Business Object,业务对象)

  • 你的理解:数据库到服务层

  • 更准确的定位:BO 主要封装业务逻辑,在服务层内部使用。它通常由一个或多个实体(Entity)组合而成,也可能包含一些业务状态或方法,用于处理复杂的业务规则。

  • 注意:数据库到服务层的直接映射通常由实体(Entity)完成(如JPA的@Entity),而不是BO。BO是服务层对实体进行业务加工后的对象。

  • 方向:仅在服务层内部流转

3. VO(View Object,视图对象)

  • 你的理解:服务层到前端

  • 更准确的定位:VO 是专门为前端视图定制的数据对象,由服务层返回给控制层,再由控制层序列化后发送给前端。它通常包含前端需要展示的字段,可能经过格式化、聚合或裁剪。

  • 与DTO的关系:在很多项目中,返回给前端的对象也常被称为DTO,但VO更强调“视图展示”的语义,而DTO更强调“数据传输”。如果严格区分,VO可以看作是服务于前端视图的DTO。

  • 方向:服务层 → 控制层 → 前端

修正后的理解

  • 前端 → 控制层:通常用DTO接收请求参数

  • 控制层 → 服务层:可使用DTO传递参数,服务层内部处理时可能转换为BO或直接操作Entity

  • 服务层内部:使用BO处理业务逻辑,可能组合多个Entity

  • 服务层 → 控制层:返回VO(或 DTO)给前端展示

  • 数据库 ↔ 服务层:通过Entity进行ORM映射,Entity 代表数据持久化对象

总结

你的基本方向是对的,但术语的精确职责和层次需要更清晰:

  • DTO是跨层传输的通用载体,常用于前端与后端、服务层之间的交互。

  • BO是服务层的业务抽象,不直接与数据库或前端耦合。

  • VO是专为前端视图定制的对象。

  • Entity才是数据库到服务层的直接映射。

在实际项目中,团队可以根据自身约定灵活命名,但保持分层清晰和数据隔离是关键。


那如果使用MyBatis多表联合查询出来的数据集,resultType不是用BO吗?

在 MyBatis 中进行多表联合查询时,返回的结果集通常不应该直接定义为 BO(业务对象),而更常见的是使用DTO(数据传输对象)或专门为该查询创建的POJO(例如命名为XxxQueryResult。下面详细解释原因和最佳实践。


1. 为什么多表查询结果不适合直接用 BO?

  • BO 的本质:业务对象(BO)是服务层内部对业务逻辑的封装,它可能包含多个实体(Entity)的组合,甚至包含业务方法。BO 的设计目的是为业务服务,而不是为数据库查询服务。

  • MyBatis 查询结果的本质:多表联合查询返回的是数据的投影(来自不同表的字段组合),通常只是原始数据的简单聚合,不包含业务行为。它应该是一个轻量的数据容器,直接映射为 POJO。

2. 应该使用什么对象?

  • DTO(数据传输对象):这是最常用的选择。DTO 用于在不同层次之间传输数据,比如从数据库层(MyBatis)到服务层,或者从服务层到控制层。多表查询的结果正是数据在不同表之间的传输,用 DTO 非常合适。

  • 自定义查询结果对象:你也可以为每个特定的多表查询定义一个 POJO,放在类似dtovo包下,命名为XxxDetailDTOXxxQueryResult等。这其实也是 DTO 的一种形式。

3. 示例说明

假设有user表和order表,需要查询用户及其最近订单的信息:

sql

SELECT u.id, u.name, o.order_no, o.amount FROM user u LEFT JOIN order o ON u.id = o.user_id WHERE ...

对应的 MyBatis 结果映射:

java

public class UserOrderDTO { private Long id; // 来自 user 表 private String name; // 来自 user 表 private String orderNo; // 来自 order 表 private BigDecimal amount; // 来自 order 表 // getters/setters }

然后在 Mapper 中:

xml

<select id="selectUserWithOrder" resultType="com.example.dto.UserOrderDTO"> ... </select>

这里的UserOrderDTO就是专为这个查询设计的 DTO,它不属于任何实体(Entity),也不包含业务逻辑,只负责承载查询结果。

4. 与 BO 的关系

  • 如果服务层需要基于这个查询结果执行复杂的业务逻辑,你可以在服务层将 DTO 转换为 BO,让 BO 继续后续的业务处理。但 MyBatis 直接返回的仍是 DTO。

  • 例如:

    java

    // Service 层 List<UserOrderDTO> dtoList = userMapper.selectUserWithOrder(...); List<UserOrderBO> boList = dtoList.stream().map(dto -> { UserOrderBO bo = new UserOrderBO(); // 可能补充其他业务字段或调用业务方法 bo.setUser(User.builder().id(dto.getId()).name(dto.getName()).build()); bo.setOrder(Order.builder().orderNo(dto.getOrderNo()).amount(dto.getAmount()).build()); return bo; }).collect(Collectors.toList());

5. 结论

  • MyBatis 多表查询的结果类型DTO 或自定义查询结果对象(放在 dto 包下)。

  • BO是服务层的业务抽象,由服务层根据 DTO 和实体(Entity)组装而成,不应该由 MyBatis 直接返回

这样做可以保持各层职责清晰:数据库层只负责数据获取,服务层负责业务组装,控制层负责视图适配。


代码示例:

服务层:Entity、BO、DTO

src/main/java/com/weiyu/service/WorkflowService.java

/** * 查询办理信息列表 */ public List<AuditInfoDTO> queryAuditInfoList(Integer workflowInstanceId) { List<AuditInfoJoinQueryDTO> queryDTOs = workflowMapper.selectAuditInfoListByJoinQuery(workflowInstanceId); // Entity -> BO,预加载所有相关数据,避免每次遍历都查询数据库 ActivityInstanceAuditContextBO auditStateDescBO = new ActivityInstanceAuditContextBO(); List<ActivityInstance> activityInstances = getActivityInstancesByWorkflowInstanceId(workflowInstanceId); List<AuditInfo> auditInfos = getAuditInfosByWorkflowInstanceId(workflowInstanceId); List<AuditLog> auditLogs = getAuditLogsByWorkflowInstanceId(workflowInstanceId); auditStateDescBO.setActivityInstances(activityInstances); auditStateDescBO.setAuditInfos(auditInfos); auditStateDescBO.setAuditLogs(auditLogs); List<AuditInfoDTO> auditInfoDTOs = new ArrayList<>(); for (AuditInfoJoinQueryDTO queryDTO : queryDTOs) { AuditInfoDTO auditInfoDTO = new AuditInfoDTO(); BeanUtils.copyProperties(queryDTO, auditInfoDTO); // 计算办理状态 String auditStateDesc = getActivityInstanceAuditStateDesc( auditStateDescBO, queryDTO.activityId(), queryDTO.auditor() ); auditInfoDTO.setAuditStateDesc(auditStateDesc); auditInfoDTOs.add(auditInfoDTO); } return auditInfoDTOs; }

控制层:DTO、VO

src/main/java/com/weiyu/controller/WorkflowController.java

/** * 查询办理信息列表 * * @param workflowInstanceId 流程实例id * @return {@link Result}&lt;{@link List}&lt;{@link AuditInfoVO}&gt;&gt; */ @GetMapping("/audit-info-list/{workflowInstanceId}") public Result<List<AuditInfoVO>> queryAuditInfoList(@PathVariable @Min(1) Integer workflowInstanceId) { String endpoint = "/workflow/audit-info-list/"; String method = "queryAuditInfoList"; log.info("【工作流程】查询办理信息列表,{}{},{}", endpoint, workflowInstanceId, method); List<AuditInfoDTO> auditInfoDTOs = workflowService.queryAuditInfoList(workflowInstanceId); // DTO -> VO List<AuditInfoVO> auditInfoVOs = auditInfoDTOs.stream() .map(dto -> { AuditInfoVO vo = new AuditInfoVO(); BeanUtils.copyProperties(dto, vo); return vo; }) .toList(); return Result.success(auditInfoVOs); }
http://www.jsqmd.com/news/473764/

相关文章:

  • 【CVPR 2025】ROD-MLLM:迈向更可靠的多模态大型语言模型中的目标检测
  • MySQL【表的约束上】
  • Intel RealSense D455 在ARM64 (Jetson) 平台上 ubuntu22.04下使用笔记
  • 2026杭电多校春季训练赛日志
  • 【effective c++】条款四十五:运用成员函数模板接受所有兼容类型
  • 安卓wakelock 学习
  • 从空白文档到完整初稿:Paperzz 如何让毕业论文写作「零门槛」通关?
  • 创新GL微电网二次控制:基于事件触发的下垂控制及其最小事件触发间隔的扰动补偿研究“(具有参考文...
  • if-else条件语句详解
  • 【深度学习代码流程】李宏毅机器学习HW-1:预测美国COVID-19阳性病率
  • MATLAB/Simulink永磁直驱风力发电系统:SVPWM空间电压矢量调制与双闭环解耦控制应用
  • 从选题到成稿零焦虑:Paperzz 毕业论文初稿写作,让学术创作告别 “卡壳式内耗”
  • 开关磁阻电机电流斩波控制仿真 simulink仿真 双闭环控制等 含有文档报告,详细的参数说明
  • Vue3 + Vite 局域网 HTTPS 访问实战:手机秒连本地开发环境
  • 2026 学术写作破局:Paperzz 如何用「四步闭环法」解决毕业论文初稿难产,让你 3 天写完合格初稿
  • 【软件测试】系统学习清单(含知识点+掌握程度拆解)
  • # Vue 实现 PDF 预览与批量打印组件
  • 论文党「反内耗」神器:Paperzz 把毕业论文初稿写成「开卷答题」,4 步搞定从 0 到成稿
  • OpenClaw Skill 操作钉钉(原理+20个实例)
  • 数据预处理骚操作
  • 自动化仓储系统的核心设备堆垛机最怕啥?急起急停带来的机械冲击。老司机都知道S型曲线速度控制才是王道,今天咱就扒一扒西门子S7-1500里的实战代码
  • 高通跃龙QCS9100平台上工业缺陷检测实战(1): 从摄像头到端侧推理的最小闭环
  • 实测负荷数据(示例)
  • 北京上门回收老安宫牛黄丸、片仔癀!本草拾光商行高价收,变现快时效拉满 - 品牌排行榜单
  • 西门子PLC精确计算设备运行时间程序(1200PLC与1500PLC通用)——改良版实时时间比较法
  • C++学习日志——面向过程篇3.11
  • 架构2
  • ADRC双环自抗扰控制永磁同步电机矢量控制伺服系统Matlab仿真探索
  • IT系统全生命周期管理和运营方案(Word)
  • PYTHON学习笔记3