后端开发 DTO-Entity-VO 转换模式详解

目录

[🧩 一、数据流走向(总览)](#🧩 一、数据流走向(总览))

[⚙️ 二、DTO → Entity:在业务层中完成](#⚙️ 二、DTO → Entity:在业务层中完成)

[⚙️ 三、Entity → VO:返回时的转换](#⚙️ 三、Entity → VO:返回时的转换)

[🧠 四、关键点总结](#🧠 四、关键点总结)

[✨ 五、推荐实践(让代码更干净)](#✨ 五、推荐实践(让代码更干净))

[1. 使用工具类简化拷贝](#1. 使用工具类简化拷贝)

[2. 把转换逻辑封装成"转换器类"](#2. 把转换逻辑封装成“转换器类”)

[✅ 六、一句话总结](#✅ 六、一句话总结)


在现代 Spring Boot Web 开发中,为了实现前后端分离、保证数据安全和明确分层,我们通常会使用三种类型的 Java 对象来处理数据:DTO、Entity 和 VO。

这个模式的核心思想是:在不同的应用层之间,使用专门的对象进行数据传输。

🧩 一、数据流走向(总览)

下面是-个标准 Web 请求的数据流转过程:

复制代码
前端 JSON 请求体
      ↓
DTO(数据传输对象)     ← 【控制器层 Controller】(用于接收输入)
      ↓
Entity(实体对象)      ← 【业务层 Service】(转换为 Entity,用于保存到数据库)
      ↓
数据库
      ↓
Entity(实体对象)      ← 【业务层 Service】(从数据库取出)
      ↓
VO(视图对象)        ← 【控制器层 Controller/Service】(转换为 VO,用于封装返回前端)
      ↓
前端 JSON 响应体

⚙️ 二、DTO → Entity:在业务层中完成

DTO (Data Transfer Object) :数据传输对象。它的唯一职责是接收前端传来的数据

当 Controller 接收到前端的 JSON 请求时,Spring MVC 会自动将其封装为 DTO 对象。

复制代码
// Controller
@PostMapping("/register")
public ApiResponse<Long> register(@Valid @RequestBody UserRegisterRequest dto) {
    // 此时,dto 对象就是前端传来的 JSON 数据
    // 然后我们把 dto 传给业务层
    Long userId = userService.register(dto);
    return ApiResponse.success("注册成功", userId);
}

Entity (实体) :数据库实体对象。它严格对应数据库中的表结构

这一步转换发生在 Service (业务) 层。Service 层负责核心业务逻辑,它接收 DTO,然后将其转换为 Entity,并补充业务所需的其他字段(比如默认权限、初始积分、加密密码等)。

复制代码
// ServiceImpl
public Long register(UserRegisterRequest dto) {
    // 1. 手动将 DTO 字段拷贝给 Entity
    User user = new User();
    user.setUsername(dto.getUsername());
    // 业务逻辑:密码需要加密
    user.setPassword(passwordEncoder.encode(dto.getPassword()));
    // 业务逻辑:昵称默认为用户名
    user.setNickname(StringUtils.hasText(dto.getNickname()) ? dto.getNickname() : dto.getUsername());
    user.setPhone(dto.getPhone());
    user.setEmail(dto.getEmail());
    
    // 2. 也可以用工具类来简化拷贝
    // BeanUtils.copyProperties(dto, user);
    // (注意:使用工具类后,仍需手动处理密码加密等特殊逻辑)
    
    // 3. 补充前端不传的业务字段
    user.setRole("user");
    user.setStatus(1);
    user.setCreditScore(100);

    // 4. 通过 MyBatis-Plus 将 Entity 存入数据库
    save(user);
    
    return user.getId();
}

这一步的作用

  1. 把前端的请求参数(DTO)转换成数据库对应的实体对象(Entity)。

  2. 在转换过程中执行业务逻辑(如加密、设置默认值)。

⚙️ 三、Entity → VO:返回时的转换

VO (View Object) :视图对象。它专门用于封装后端需要返回给前端的数据

我们通常不会 直接把 Entity 返回给前端,因为 Entity 里可能包含敏感字段(比如 passwordsalt)或者前端不需要的字段(比如 is_deletedupdate_time)。

VO 的作用就是只挑选前端需要的字段。

示例:

复制代码
// 专门用于前端展示的 UserVO
public class UserVO {
    private Long id;
    private String username;
    private String nickname;
    private String avatar;
    private String email;
}

转换可以在 Service 层完成,也可以在 Controller 层完成(推荐在 Service 层)。

复制代码
// ServiceImpl 中...
// 假设我们从数据库查到了 user (Entity)
User user = getById(userId);

// 1. 转换为 VO
UserVO vo = new UserVO();
vo.setId(user.getId());
vo.setUsername(user.getUsername());
vo.setNickname(user.getNickname());
vo.setAvatar(user.getAvatar());
vo.setEmail(user.getEmail());

// 2. 或者同样用工具类
// BeanUtils.copyProperties(user, vo);

// 3. 在 Controller 返回 VO
return ApiResponse.success("查询成功", vo);

前端最终拿到的 JSON 就会非常干净:

复制代码
{
  "success": true,
  "message": "查询成功",
  "data": {
    "id": 1001,
    "username": "tom",
    "nickname": "Tom",
    "avatar": "avatar/default.png",
    "email": "tom@demo.com"
  }
}

🧠 四、关键点总结

转换方向 发生位置 意义
DTO → Entity Service 把前端请求数据转换为数据库对象(用于保存
Entity → VO ServiceController 把数据库对象转换为前端可展示的数据(用于返回

✨ 五、推荐实践(让代码更干净)

1. 使用工具类简化拷贝

手动 setget 非常繁琐且容易出错。

  • Spring 自带 : BeanUtils.copyProperties(source, target);

  • 常用增强库:

    • MapStruct(推荐):在编译时自动生成类型安全的转换代码,性能极高。

    • ModelMapper:在运行时通过反射自动映射,非常灵活但性能略慢。

2. 把转换逻辑封装成"转换器类"

当转换逻辑变多时,可以创建一个专门的 Convert 类(或接口),让 Service 层保持干净。

复制代码
public class UserConvert {

    // 使用 MapStruct 的示例
    // @Mapper(componentModel = "spring")
    // public interface UserConvert {
    //     UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
    //     User toEntity(UserRegisterRequest dto);
    //     UserVO toVO(User user);
    // }

    // 手动封装的示例
    public static User toEntity(UserRegisterRequest dto) {
        User user = new User();
        BeanUtils.copyProperties(dto, user);
        // ... 其他特殊处理
        return user;
    }

    public static UserVO toVO(User user) {
        UserVO vo = new UserVO();
        BeanUtils.copyProperties(user, vo);
        return vo;
    }
}

然后你的 Service 代码就可以简化为:

复制代码
// ServiceImpl
User user = UserConvert.toEntity(request);
// ... 处理加密等 ...
save(user);
return UserConvert.toVO(user);

✅ 六、一句话总结

环节 作用 代码位置
DTO 接收前端输入 Controller 入参
Entity 持久化到数据库 Service + Mapper
VO 返回前端展示 Controller 出参
DTO→Entity 在 Service 中转换 业务逻辑处理时
Entity→VO 在 Service 或 Controller 中转换 返回结果时
相关推荐
shuxiaohua2 天前
使用HttpURLConnection调用SSE采坑记录
状态模式
崎岖Qiu2 天前
状态模式与策略模式的快速区分与应用
笔记·设计模式·状态模式·策略模式·开闭原则
Jonathan Star3 天前
前端需要做单元测试吗?哪些适合做?
前端·单元测试·状态模式
一水鉴天4 天前
整体设计 全面梳理复盘 之40 M3 统摄三层 AI 的动态运营社区(Homepage)设计
架构·transformer·状态模式·公共逻辑
前端玖耀里7 天前
Vue + Axios + Node.js(Express)如何实现无感刷新Token?
状态模式
将编程培养成爱好7 天前
C++ 设计模式《外卖骑手状态系统》
c++·ui·设计模式·状态模式
向葭奔赴♡8 天前
Spring Boot参数校验全流程解析
状态模式
阿珊和她的猫8 天前
Webpack 打包体积优化:让应用更轻量、更高效
前端·webpack·状态模式
Jonathan Star10 天前
在 LangFlow 中,**节点(Node)是构成工作流的核心基本单元**
状态模式
WYiQIU10 天前
大厂前端岗重复率极高的场景面试原题解析
前端·javascript·vue.js·react.js·面试·状态模式