DTO 的全称是 Data Transfer Object,中文意思是“数据传输对象”。

简单来说,DTO 就是一个专门用来在不同层(比如 Controller 层、Service 层,或者前端和后端)之间传递数据的简单对象(通常是 POJO - Plain Old Java Object)。

为什么需要 DTO?

想象一下,你的应用程序有不同的模块或层次:

表现层 (Presentation Layer):比如你的 Controller,负责接收前端请求和返回响应。业务逻辑层 (Service Layer):比如你的 Service 类,负责处理核心业务逻辑。数据访问层 (Data Access Layer):比如你的 Mapper 或 DAO,负责与数据库交互。领域模型 (Domain Model):比如你的数据库实体类(Entity),它们直接映射数据库表结构。

现在,考虑以下场景:

场景1:减少不必要的网络调用或方法调用

(这个场景在微服务架构或分布式系统中更明显,但在单体应用的不同层之间也有类似概念)假设你的 Service 层有一个方法需要从 Controller 层获取 5 个相关但不属于同一个核心实体的数据片段。如果 Controller 分 5 次调用 Service 层的方法来传递这 5 个数据,会比较繁琐。DTO 可以将这 5 个数据打包成一个对象,一次传递。

场景2:隔离领域模型与外部接口(非常重要!)

你的数据库实体类(Entity,例如 Employee 实体,包含很多字段如 id, username, password, name, status, createTime, updateTime 等)通常包含了所有与数据库表对应的字段,甚至还有一些与持久化相关的注解(如 JPA 的 @Entity, @Table, @Id)。问题:

直接暴露内部结构:如果 Controller 直接接收或返回数据库实体类,那么你的 API 就直接暴露了数据库表结构。如果将来数据库表结构变了(比如增加了一个内部使用的字段),你的 API 也会跟着变,这会破坏 API 的稳定性。安全性:数据库实体可能包含敏感信息(如密码哈希、内部状态字段),这些信息不应该直接传递给前端或外部调用者。数据冗余或缺失:前端可能只需要实体的一部分数据(比如员工列表只需要 id, name, username),或者前端传过来的数据只是用来创建或更新实体的一部分(比如登录只需要 username 和 password)。直接使用实体类可能会传输过多不必要的数据,或者实体类的结构不完全匹配前端的需求。

DTO 的解决方案:

创建一个 DTO,它只包含特定操作所必需的字段。例如,员工登录可能用 EmployeeLoginDTO,它只有 username 和 password 字段。新增员工可能用 EmployeeDTO(或者更精确的 CreateEmployeeDTO),它包含 username, name, phone, sex, idNumber 等字段,但不包含 id (由后端生成) 或 status, createTime (由后端设置)。查询员工列表时,返回的可能是 EmployeeVO (Value Object,有时 DTO 和 VO 会被泛指,但 VO 更侧重于展示层需要的数据),它只包含前端展示所需的字段。

场景3:API 的版本控制和演进

使用 DTO 作为 API 的数据契约,可以让你在内部领域模型变化时,保持 API 的向后兼容性。你可以通过 DTO 到领域模型的映射来适配这些变化。

DTO 的主要特点

简单数据容器:它通常只包含私有字段(属性)以及对应的 getter 和 setter 方法。没有业务逻辑:DTO 本身不应该包含任何业务处理逻辑。它的唯一职责就是携带数据。序列化能力:因为经常需要在网络间传输(如 Controller 返回给前端)或进程间传递,DTO 对象通常需要是可序列化的。贫血模型 (Anemic Domain Model):DTO 本身就是贫血模型的典型代表,只有数据,没有行为。

在"苍穹外卖"项目中的例子

你在苍穹外卖项目中会看到很多以 DTO 结尾的类,例如:

EmployeeDTO.java:可能用于管理员新增或修改员工时,封装前端传递过来的员工信息。

// 简化示例

@Data // Lombok注解,自动生成getter, setter, toString等

public class EmployeeDTO implements Serializable {

private Long id; // 修改时可能会用到

private String username;

private String name;

private String phone;

private String sex;

private String idNumber;

// 可能还有其他字段...

}

当管理员在前端页面填写员工信息并点击“保存”时,前端会将这些信息构造成一个 JSON 对象发送给后端。后端的 Controller 方法会使用 @RequestBody EmployeeDTO employeeDTO 来接收这个 JSON,并将其自动转换为 EmployeeDTO 对象。

EmployeeLoginDTO.java:用于员工登录时,封装用户名和密码。

@Data

public class EmployeeLoginDTO implements Serializable {

private String username;

private String password;

}

EmployeePageQueryDTO.java:用于员工分页查询时,封装查询条件(如姓名、页码、每页记录数)。

@Data

public class EmployeePageQueryDTO implements Serializable {

private String name; // 查询条件:员工姓名

private int page; // 页码

private int pageSize;// 每页显示记录数

}

Controller 中如何使用 DTO

@RestController

@RequestMapping("/admin/employee")

public class EmployeeController {

@Autowired

private EmployeeService employeeService;

@PostMapping

@ApiOperation("新增员工")

public Result save(@RequestBody EmployeeDTO employeeDTO) { // <--- 接收DTO

log.info("新增员工:{}", employeeDTO);

employeeService.save(employeeDTO); // Service层方法可能也接收DTO,或者在Service层将DTO转换为Entity

return Result.success();

}

@PostMapping("/login")

@ApiOperation("员工登录")

public Result login(@RequestBody EmployeeLoginDTO employeeLoginDTO) { // <--- 接收登录DTO

log.info("员工登录:{}", employeeLoginDTO);

Employee employee = employeeService.login(employeeLoginDTO); // Service层处理登录逻辑

// ... 构建 EmployeeLoginVO (VO: Value Object,用于返回给前端的数据对象)

// EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()...build();

// return Result.success(employeeLoginVO);

return Result.success(); // 简化

}

@GetMapping("/page")

@ApiOperation("员工分页查询")

public Result page(EmployeePageQueryDTO employeePageQueryDTO) { // <--- 接收分页查询DTO (GET请求通常不用@RequestBody)

log.info("员工分页查询,参数为:{}", employeePageQueryDTO);

PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);

return Result.success(pageResult);

}

}

DTO 与 Entity 的转换

在 Service 层,通常需要将接收到的 DTO 对象转换为数据库实体对象 (Entity) 以便进行持久化操作,或者将从数据库查询出来的 Entity 对象转换为 DTO (或 VO) 以便返回给 Controller 层。

这个转换可以通过以下方式完成:

手动逐个字段 set。使用工具类,如 Spring 的 BeanUtils.copyProperties(source, target)。使用专门的映射框架,如 MapStruct (推荐,编译期生成代码,性能好且类型安全)。

总结:

DTO 是一个简单的数据载体,用于解耦应用的不同层(尤其是表现层和业务层/领域模型),定义清晰的 API 数据契约,提高安全性和灵活性。 它是现代分层架构和微服务设计中非常常见的模式。

Copyright © 2088 竞技新视野 - 电竞赛事活动专题站 All Rights Reserved.
友情链接