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)
相关推荐
这里有鱼汤25 分钟前
“三角收敛”战法全解析:我靠这一招实现了年化35%
后端·python
小鱼人爱编程1 小时前
Java基石--Java发动机ClassLoader
java·spring boot·后端
一只叫煤球的猫1 小时前
从屎山说起:支付流程重构实战,三种设计模式灵活运用
java·后端·架构
xiezhr1 小时前
那些年我们一起追过的Java技术,现在真的别再追了!
java·后端·编程语言
Victor3561 小时前
MySQL(155)什么是MySQL的事件调度器?
后端
Victor3562 小时前
MySQL(156)如何使用MySQL的事件调度器?
后端
程序员爱钓鱼3 小时前
Go语言实战案例-使用map实现学生成绩管理
后端·google·go
程序员爱钓鱼3 小时前
Go语言实战案例-合并多个文本文件为一个
后端·google·go
Microsoft Word7 小时前
用户中心项目实战(springboot+vue快速开发管理系统)
vue.js·spring boot·后端
不写八个9 小时前
GoLang教程005:switch分支
开发语言·后端·golang