详解DTO
DTO是什么?
DTO(Data Transfer Object,数据传输对象)是一种数据传输模型,用于在前端与后端或不同服务之间传递数据。它本质上是一个专门用来传输数据的类,不包含任何业务逻辑。
DTO 与实体类 (Entity) 的区别
特点 | DTO | 实体类(Entity) |
用途 | 专门用于数据传输,简化数据结构,防止暴露内部模型 | 主要用于表示数据库表的数据模型,包含完整的数据结构 |
数据字段 | 通常只保留必要字段,字段较少 | 字段较多,包含完整的数据库映射信息 |
业务逻辑 | 无业务逻辑,仅用于传输数据 | 可能包含一些业务逻辑 |
是否与数据库关联 | 不直接与数据库关联,纯粹为数据传输服务 | 直接映射数据库表,包含 @Entity、@Table等注解 |
敏感信息保护 | 可以隐藏不应暴露的数据 (如密码、加密数据) | 可能包含敏感数据,直接传给前端可能有安全风险 |
为什么要用 DTO?
✅ 1. 安全性
DTO 允许你筛选数据,避免将敏感信息(如密码、加密数据)直接暴露给前端。
✅ 2. 提高性能
DTO 只传输必要数据,减少网络传输的数据量。
✅ 3. 降低耦合
DTO 将业务逻辑、数据模型与数据库模型解耦,使得系统更灵活,方便后期扩展和维护。
✅ 4. 数据转换方便
DTO 允许在返回结果时重新组织数据结构,满足不同的前端需求。
DTO 和实体类的合作模式
🚀 典型流程
1.Controller 层:接收请求数据并将其封装到 DTO 中。
2.Service 层:将 DTO 转换成实体类 (Entity),并执行业务逻辑。
3.Repository (DAO) 层:使用 Entity 与数据库交互。
4.Controller 层:将 Entity 转换回 DTO 并返回给前端。
图解:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
前端 = 前端 = 前端 = 前端 = 前端 = 前端
↓ 请求报文 ↑ 响应报文
Controller层 :接收 [请求报文] → 解析并封装到 [DTO] 中 接收 [DTO] 并转为符合前端要求的 [响应报文]
↓ ↓ ↑
Service层:将 [DTO] 转换为 [Entity] + 执行业务逻辑 将 [Entity] 转换为 [DTO] + 执行业务逻辑 + 数据处理(过滤/转格式等)
↓ ↓ ↑
DAO层: 使用 [Entity] 与数据库交互 读取数据库数据并封装为[Entity]对象
↓ ↓ ↑
数据库 = 数据库 = 数据库 = 数据库 = 数据库
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
代码示例:
🎯 Step 1:实体类(Entity)
@Entity @Table(name = "orders") @Data public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String orderId; private BigDecimal amount; private String customerName; private String customerPhone; private String internalRemark; // 内部备注(不应该返回给前端) }
🎯 Step 2:DTO 类
@Data public class OrderDTO { private String orderId; private BigDecimal amount; private String customerName; }
🚨 注意:DTO 里没有customerPhone、internalRemark,这些数据不需要返回给前端。
🎯 Step 3:Service 层 - DTO 和实体类的转换
@Service public class OrderService { @Autowired private OrderRepository orderRepository; // 新增订单 public OrderDTO createOrder(OrderDTO orderDTO) { // DTO -> Entity (数据转换) Order order = new Order(); order.setOrderId(orderDTO.getOrderId()); order.setAmount(orderDTO.getAmount()); order.setCustomerName(orderDTO.getCustomerName()); // 保存到数据库 Order savedOrder = orderRepository.save(order); // Entity -> DTO (数据转换) OrderDTO responseDTO = new OrderDTO(); responseDTO.setOrderId(savedOrder.getOrderId()); responseDTO.setAmount(savedOrder.getAmount()); responseDTO.setCustomerName(savedOrder.getCustomerName()); return responseDTO; } }
🎯 Step 4:Controller 层
@RestController @RequestMapping("/api/orders") public class OrderController { @Autowired private OrderService orderService; @PostMapping("/create") public ResponseEntity<OrderDTO> createOrder(@RequestBody OrderDTO orderDTO) { OrderDTO createdOrder = orderService.createOrder(orderDTO); return ResponseEntity.ok(createdOrder); } }
🎯 Step 5:请求和响应示例
✅ 请求数据(Request Body)
{ "orderId": "123456", "amount": 250.75, "customerName": "张三" }
✅ 响应数据(Response Body)
{ "orderId": "123456", "amount": 250.75, "customerName": "张三" }
🚨 注意:customerPhone 和 internalRemark 并没有暴露在响应中。
工具类简化 DTO/Entity 转换 (推荐)
为了减少 DTO 和实体类的字段映射代码,可以使用以下工具:
✅ MapStruct (推荐)
✅ ModelMapper
✅ BeanUtils.copyProperties()(Spring 提供的简单工具)
🎯 使用 MapStruct 示例
DTO 类
@Mapper(componentModel = "spring") public interface OrderMapper { Order toEntity(OrderDTO orderDTO); OrderDTO toDTO(Order order); }
Service 层 (使用 Mapper)
@Service public class OrderService { @Autowired private OrderRepository orderRepository; @Autowired private OrderMapper orderMapper; public OrderDTO createOrder(OrderDTO orderDTO) { Order order = orderMapper.toEntity(orderDTO); Order savedOrder = orderRepository.save(order); return orderMapper.toDTO(savedOrder); } }
🚨 什么时候用 DTO?
✅ 数据传输时,DTO 提高安全性、减少数据冗余
✅ 多数据源、多业务场景时,DTO 提高了数据灵活性
✅ 在微服务架构中,DTO 非常适合用于服务间通信
🚨 什么时候用 Entity?
✅ 数据库表的持久化操作
✅ 需要与 JPA、MyBatis 等框架交互时
🚨 最佳实践
在 Controller 与 Service 之间,使用 DTO。
在 Service 与 Repository 之间,使用 Entity。
使用工具类(如 MapStruct)简化 DTO ↔ Entity 转换,提高代码的整洁度。
🔍 进一步理解:DTO 的本质
DTO 本质上就是一个数据容器,类似一个快递盒子,负责:
1.装数据(把数据封装起来)。
2.传数据(让 Controller 和前端/其他服务交换数据)。
DTO 里通常不包含复杂的业务逻辑,更多是对数据的组织和筛选。
💡 为什么需要 DTO?光用实体类不行吗?
在很多小项目中,直接用实体类 (Entity) 也可以完成数据传输。但是在以下场景下,DTO 更加合适:
🚨 1. 安全性
- 实体类往往与数据库结构一一对应,可能包含敏感字段(如密码、加密信息、内部备注等)。
- DTO 通过数据筛选,仅暴露必要数据,避免敏感信息泄露。
✅ 示例
Entity (实体类)
@Entity public class User { private Long id; private String username; private String password; // 🚨 敏感字段 private String internalNote; // 🚨 内部备注,前端不需要 }
DTO
public class UserDTO { private String username; }
返回结果 (使用 DTO)
{ "username": "zhangsan" }
DTO 成功隐藏了password和internalNote,更安全。
🚨 2. 数据结构不一致
- 前端页面往往不需要完整的实体数据,DTO 能按需组织数据,减少无关数据的传输。
- 复杂的业务场景下,DTO 可以将多张表的数据整合到一个对象中,提高数据传输效率。
✅ 示例:数据整合假设前端需要订单信息+客户信息,DTO 就可以将两部分数据整合成一个对象:
OrderDTO (整合了订单和客户信息)
public class OrderDTO { private String orderId; private BigDecimal totalAmount; private String customerName; private String customerPhone; }
在Service层将Order、Customer数据整合到OrderDTO中,避免前端多次请求。
🚨 3. 数据转换
- DTO 允许在返回数据时更改字段名、重新组合数据,满足不同客户端的需求。
- DTO 也可以对数据做格式转换,例如时间格式转换、金额格式化等。
✅ 示例:时间格式转换Entity (数据库中的时间戳)
private LocalDateTime createTime; // 数据库存储
DTO (转换为字符串形式)
private String createTime; // 返回给前端的格式
在 Service 层将 LocalDateTime 转换为 String,让前端更容易展示。
🚨 4. API 版本控制
- DTO 便于在接口升级时增加新字段或调整数据结构,而不影响原有实体类。
- 通过 DTO,接口兼容性更强,便于维护。
✅ 示例:API 版本升级
public class UserV1DTO { private String username; } public class UserV2DTO { private String username; private String email; // 新增字段 }
🚨 5. 解耦性
- Entity 类通常与数据库表结构密切相关,DTO 则是纯粹的数据模型,与持久层逻辑无关。
- DTO 解耦了数据模型与业务逻辑,避免前端直接接触数据库模型,提高系统的稳定性和灵活性。
⚙️ 总结:DTO 的本质
✅ DTO 主要用于数据传输。
✅ DTO 通过筛选、转换、整合数据,满足前端或其他服务的需求。
✅ DTO 能够避免将敏感信息暴露在 API 接口中,提高安全性。
虽然 DTO 只是一个“装数据的盒子”,但它的作用非常重要,尤其在大型项目、复杂业务场景下,DTO 能显著提升代码的安全性、灵活性和可维护性。