后端开发 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 中转换 返回结果时
相关推荐
野生技术架构师2 小时前
Java面试题及答案总结(互联网大厂新版)
java·面试·状态模式
. . . . .2 小时前
备份11111
状态模式
Light604 小时前
【MCP原生时代】第2篇|前端如何舞动 MCP:新一代交互范式——从 Hook 到流式渲染,打造 AI 原生前端体验
状态模式·前端架构·mcp·react hook·流式渲染·ai交互
阿珊和她的猫18 小时前
实现资源预加载:提升网页性能与用户体验
状态模式·ux
列星随旋21 小时前
minio分片上传
状态模式
我爱学习_zwj1 天前
前端设计模式:轻量级实战指南
设计模式·前端框架·状态模式
LSL666_1 天前
9 前后端数据处理格式的注意事项
状态模式·请求·响应
仪***沿2 天前
Fluent中颗粒流模拟的门道
状态模式
GDAL2 天前
前端保存用户登录信息 深入全面讲解
前端·状态模式
柯南二号2 天前
【大前端】【Android】把 Activity 重构成 MVVM 的对比示例
android·状态模式