DO,VO,DTO 傻傻分不清楚

其实不止DO,VO,DTO,还有 BO,PO 等等一大堆"欧",目前暂时先用这个三个吧,记录一下,下次重构项目时候按照这个梳理。

📦 项目结构示例

text 复制代码
com.example.project
├── user/
│   ├── controller/
│   ├── service/
│   ├── dao/
│   ├── domain/
│   │   ├── form/               ← 请求参数接收和校验对象
│   │   │   ├── InsertUserForm.java
│   │   │   ├── LoginForm.java
│   │   ├── dto/                ← 用于 Service 层业务传输
│   │   │   ├── InsertUserDTO.java
│   │   ├── vo/                 ← 给前端展示的数据模型
│   │   │   ├── UserVO.java
│   │   ├── do/                 ← 数据库映射对象
│   │   │   ├── UserDO.java
│   │   └── convert/            ← 数据对象之间的转换工具类
│   │       ├── UserConvert.java

🧭 请求流程总览

/user/create 为例:

text 复制代码
[HTTP Request]   
    ↓
[Controller 层]
    |                [InsertUserForm] ← 接收参数(校验注解)
    ↓                [InsertUserDTO]  ← Controller 和 Service 层之间的传输对象            
[Service 层]
    |                [UserDO]         ← DO,映射数据库字段,用于存储
    ↓            或者[UserQueryDTO]   ← 对于查询参数等的场景,DTO不需要转成DO,直接传入DAO层即可  
[DAO 层]
    ↓  
[数据库]
    ↓
[DAO 层]
    ↓ 
Service
    |                [UserDO]         ← 查询结果
    |            或者[UserSummaryVO]  ← 查询返回部分字段、统计字段、拼接字段...
    ↓            或者[UserSummaryDTO] ← 查询返回部分字段、统计字段、拼接字段...
Controller
    ↓                [UserVO]        ← Controller 转换成展示结构
[HTTP Response] 

🧩 各层对象职责

1⃣ Controller 层:接收请求 + 参数校验

  • 类位置:user/controller/UserController.java
  • 使用对象:InsertUserForm(位于 user/domain/form/)
java 复制代码
@PostMapping("/create")
public R createUser(@Valid @RequestBody InsertUserForm form) {
    InsertUserDTO dto = UserConvert.INSTANCE.formToDTO(form);
    userService.insertUser(dto);
    return R.ok();
}

转换发生:

  • InsertUserFormInsertUserDTO
  • 利用 UserConvert 做数据映射

目的:

  • 接收并校验前端数据(如用户名非空、邮箱格式)
  • 提供处理后的数据给业务逻辑层使用(比如有的前端传来的字段,需要处理后才能给业务使用)

2⃣ Service 层:执行业务逻辑 + 转换为持久对象

  • 类位置:user/service/UserServiceImpl.java
  • 使用对象:InsertUserDTOUserDO(位于 user/domain/do/
    • 如果是其他操作,直接用 xxxDTO(查询参数等的场景,DTO不需要转成DO,直接传入DAO层即可)
    • 例如:UserQueryDTO
java 复制代码
public void insertUser(InsertUserDTO dto) {
    UserDO userDO = UserConvert.INSTANCE.dtoToDO(dto);
    userDao.insert(userDO);
}

转换发生:

  • DTODO
  • 可设置一些默认值(注册时间、状态字段)

目的:

  • 业务逻辑干净,不污染 Controller 层结构
  • DO 是数据库映射对象,与表结构一一对应

3⃣ DAO 层:操作数据库

  • 类位置:user/dao/UserDao.java
  • 使用对象:UserDO
    • 或者返回结果映射到 UserSummaryVO(查询返回部分字段、统计字段、拼接字段...)
    • 或者返回结果映射到 UserSummaryDTO(查询返回部分字段、统计字段、拼接字段...)

假如创建完用户后要返回用户详情:

java 复制代码
UserVO vo = UserConvert.INSTANCE.doToVO(userDO);
return R.ok(vo);

转换发生:

  • DOVO
  • VO 可格式化时间、显示标签文案等,仅做展示

DAO 执行数据库语句后,返回结果可能映射到 DO,DTO,VO:

情况一:DAO 返回的是 DO(数据库映射对象)

需要在接下来的 Service 层,转换为业务对象(DTO)或展示对象(VO)

例如 DAO 接收到数据库返回的 UserDO,接下来在 Service 层:

java 复制代码
UserDO userDO = userDao.findById(id);
UserDTO dto = UserConvert.INSTANCE.doToDTO(userDO);      // 用于业务逻辑处理(脱敏、格式化等等)
UserVO vo = UserConvert.INSTANCE.doToVO(userDO);         // 用于返回前端展示

情况二:DAO 直接返回 VO(展示对象)

无需转换(直接返回),可直接用作 Controller 响应数据。

例如 DAO 接收到数据库返回的 UserListVO,途径 Service 层,一路回传到 Controller 层后:

java 复制代码
List<UserListVO> voList = userDao.queryUserList();  // VO 结构已拼接好字段
return R.ok(voList);                                 // 直接响应前端
  • 适合场景:
    • 只查部分字段
    • SQL 已完成业务拼接或展示字段格式处理(如联表查询)

⚠️ 注意:这种设计要小心 VO 被 "SQL 耦合污染",不建议太复杂的展示结构直接从 DAO 出。

情况三:DAO 返回 DTO(中间业务对象)

例如 DAO 接收到数据库返回的 UserDTO,可以在 Service 层或 Controller 层(就 Service 层吧)转为 VO,用于展示:

java 复制代码
List<UserDTO> dtoList = userDao.selectActiveUsers();
List<UserVO> voList = dtoList.stream()
    .map(UserConvert::dtoToVO)
    .collect(Collectors.toList());
return R.ok(voList);

这种做法适用于复杂业务逻辑之后还要做展示格式转换的情况。DTO 保持业务干净,VO 负责展示语义。

转换方法(convert/ 包)

Java 业内时间搞出来这么多实体类,还经常要转来转去,那肯定是封装一下对象间转换工具类比较好了,不然太折磨人了(好吧,现在这么多实体类已经很折磨人了)

梳理一下每个对象的转换类要实现哪些方法:

  • Form → DTO(前端请求参数 → 业务传输对象)
  • DTO → DO (业务参数 → 数据库持久对象)
  • DO → VO (数据库对象 → 展示对象)
  • DTO → VO (业务结果 → 展示结构)

而且啊:每一种明显职责不一致的转换都单独写方法,尤其字段不完全对得上、或者需要格式化的场景。

比如 UserConvert.java(使用 MapStruct 可以自动生成转换实现,无需手写 Getter/Setter 代码):

java 复制代码
@Mapper
public interface UserConvert {

    UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);

    // form -> dto
    InsertUserDTO formToDTO(InsertUserForm form);

    // dto -> do
    UserDO dtoToDO(InsertUserDTO dto);

    // do -> vo
    UserVO doToVO(UserDO userDO);

    // dto -> vo(有些业务逻辑后直接转展示结构)
    UserVO dtoToVO(UserDTO dto);
}

✅ 总结

层级 对象类型 作用 转换工具
Controller Form 接收 + 校验 UserConvert.formToDTO()
Service DTO → DO 业务处理 → 数据落库 UserConvert.dtoToDO()
DAO DO 与数据库交互 -
Controller DO/DTO → VO 结果展示 UserConvert.doToVO()
Controller VO → R 响应封装 R.ok(vo)
相关推荐
earthzhang20214 小时前
第3讲:Go垃圾回收机制与性能优化
开发语言·jvm·数据结构·后端·性能优化·golang
thinktik6 小时前
AWS EKS 集成Load Balancer Controller 对外暴露互联网可访问API [AWS 中国宁夏区]
后端·kubernetes·aws
追逐时光者7 小时前
将 EasySQLite 解决方案文件格式从 .sln 升级为更简洁的 .slnx
后端·.net
驰羽7 小时前
[GO]GORM 常用 Tag 速查手册
开发语言·后端·golang
AntBlack8 小时前
虽迟但到 :盘一盘 SpringAI 现在发展得怎么样了?
后端·spring·openai
ss2738 小时前
手写Spring第4弹: Spring框架进化论:15年技术变迁:从XML配置到响应式编程的演进之路
xml·java·开发语言·后端·spring
舒一笑9 小时前
🚀 PandaCoder 2.0.0 - ES DSL Monitor & SQL Monitor 震撼发布!
后端·ai编程·intellij idea
Java中文社群9 小时前
服务器被攻击!原因竟然是他?真没想到...
java·后端
helloworddm10 小时前
Orleans 流系统握手机制时序图
后端·c#
开心-开心急了11 小时前
Flask入门教程——李辉 第三章 关键知识梳理
后端·python·flask