Java中DTO和VO的区别举例详解
作者:趁你还年轻_
在Java开发中,VO和DTO都是用于数据传输的对象,但它们的含义和用途有所不同,这篇文章主要介绍了Java中DTO和VO区别的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
前言
DTO(Data Transfer Object)和 VO(Value Object / View Object)是 Java 开发中用于不同层次间数据传递的对象,但它们的设计目的和使用场景有本质区别。
一、概念定义
DTO(Data Transfer Object)
定义:用于在不同层或系统之间传输数据的对象
核心作用:减少远程调用次数,打包多个数据一次性传输
典型场景:Service 层返回数据给 Controller 层、微服务间调用
VO(Value Object / View Object)
定义:用于前端展示的数据对象,封装页面需要的数据
核心作用:适配前端展示需求,隐藏后端敏感字段
典型场景:Controller 层返回给前端
二、核心区别对比表
| 对比维度 | DTO (Data Transfer Object) | VO (View Object) |
|---|---|---|
| 设计目的 | 减少网络调用,批量传输数据 | 适配前端展示需求 |
| 数据范围 | 可能包含多个聚合的数据 | 针对单个页面/组件定制 |
| 数据敏感度 | 可包含内部字段 | 必须隐藏敏感字段(密码、金额等) |
| 使用层次 | Service ↔ Controller、微服务间调用 | Controller ↔ 前端 |
| 字段格式化 | 通常是原始数据类型 | 包含格式化后的数据(如日期、金额显示格式) |
| 变更频率 | 相对稳定 | 随前端需求频繁变化 |
三、详细说明与代码示例
场景:用户订单详情查询
数据库实体(DO - Data Object)
@Entity
@Table(name = "t_user")
public class UserDO {
private Long id;
private String username;
private String password; // 敏感字段
private String email;
private String phone;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Integer status; // 0-注销 1-正常 2-冻结
}DTO(Service 层返回)
@Data
public class UserOrderDTO {
// 用户基本信息
private Long userId;
private String username;
private String email; // 可能包含敏感信息
// 订单列表(跨聚合数据)
private List<OrderDTO> orders;
// 统计信息
private Integer totalOrderCount;
private BigDecimal totalAmount;
// 原始数据类型
private LocalDateTime userCreateTime;
}使用场景:Service 层查询用户和订单数据,打包返回给 Controller
@Service
public class UserOrderService {
public UserOrderDTO getUserWithOrders(Long userId) {
UserDO user = userDao.findById(userId);
List<OrderDO> orders = orderDao.findByUserId(userId);
// 转换并聚合数据
UserOrderDTO dto = new UserOrderDTO();
dto.setUserId(user.getId());
dto.setUsername(user.getUsername());
dto.setEmail(user.getEmail()); // 直接暴露原始邮箱
dto.setOrders(convertOrders(orders));
dto.setTotalOrderCount(orders.size());
return dto;
}
}VO(Controller 层返回前端)
@Data
public class UserOrderVO {
// 前端展示字段(脱敏)
private Long userId;
private String username;
private String maskedEmail; // 脱敏:a****@gmail.com
// 订单数据(按需展示)
private List<OrderVO> orders;
// 格式化后的数据
private String formattedTotalAmount; // "¥9,999.00"
private String userCreateTimeDesc; // "2025-01-15 12:00"
// 前端状态(计算得出)
private String userStatusText; // "正常" 而非 1
// 不包含:password, phone, raw status
}使用场景:Controller 层转换 DTO 为 VO,适配前端展示
@RestController
@RequestMapping("/api/users")
public class UserOrderController {
@Resource
private UserOrderService userOrderService;
@GetMapping("/{userId}/orders")
public ResultVO<UserOrderVO> getUserOrders(@PathVariable Long userId) {
UserOrderDTO dto = userOrderService.getUserWithOrders(userId);
// DTO → VO 转换(关键一步!)
UserOrderVO vo = new UserOrderVO();
vo.setUserId(dto.getUserId());
vo.setUsername(dto.getUsername());
// 1. 脱敏处理
vo.setMaskedEmail(maskEmail(dto.getEmail())); // a****@gmail.com
// 2. 数据格式化
vo.setFormattedTotalAmount(formatCurrency(dto.getTotalAmount()));
vo.setUserCreateTimeDesc(formatDateTime(dto.getUserCreateTime()));
// 3. 状态转换
vo.setUserStatusText(convertStatusToText(user.getStatus())); // "正常"
// 4. 按需过滤字段
vo.setOrders(convertToOrderVO(dto.getOrders()));
return ResultVO.success(vo);
}
}四、常见误区与反例
❌ 误区 1:DTO 和 VO 混用
// 错误:Controller 直接返回 DTO,暴露敏感信息
@RestController
public class UserController {
public UserOrderDTO getUserOrders(Long userId) {
return userOrderService.getUserWithOrders(userId); // ❌ 暴露了 password 等字段
}
}❌ 误区 2:VO 包含业务逻辑
// 错误:VO 包含计算逻辑
@Data
public class UserOrderVO {
private BigDecimal orderAmount;
private BigDecimal discount;
// ❌ VO 只应承载数据,不应有业务方法
public BigDecimal getPayAmount() {
return orderAmount.subtract(discount); // 计算应在后端完成
}
}❌ 误区 3:过度使用 DTO 导致类爆炸
// 错误:为每个方法都创建 DTO
public class UserOrderService {
public UserOrderDTO1 method1() { ... }
public UserOrderDTO2 method2() { ... }
public UserOrderDTO3 method3() { ... } // 维护噩梦
}✅ 正确做法:合理复用 DTO,仅在字段差异大时创建新类
五、与其他对象的关系
在分层架构中,完整的对象体系如下:

完整对象体系
| 缩写 | 全称 | 作用位置 | 示例 |
|---|---|---|---|
| DO | Data Object | 持久层 ↔ 数据库 | UserDO(对应数据库表) |
| DTO | Data Transfer Object | Service ↔ Controller | UserOrderDTO(聚合数据) |
| VO | View Object | Controller ↔ 前端 | UserOrderVO(脱敏+格式化) |
| BO | Business Object | Service 层内部 | OrderBO(处理业务逻辑) |
| PO | Persistant Object | 同 DO(老叫法) | UserPO |
| AO | Application Object | 应用层参数 | LoginAO(登录参数) |
六、最佳实践总结
1. 转换时机明确
// 分层转换 DAO层 → Service层 → Controller层 → 前端 DO → DTO → VO → JSON
2. 命名规范
// DTO 命名:业务 + DTO UserDTO, OrderDTO, UserOrderDTO // VO 命名:页面/组件 + VO OrderListVO, OrderDetailVO, UserProfileVO
3. 工具类辅助转换
@Component
public class UserConverter {
// DO → DTO
public UserDTO toDTO(UserDO userDO) {
UserDTO dto = new UserDTO();
BeanUtils.copyProperties(userDO, dto);
return dto;
}
// DTO → VO(关键:脱敏+格式化)
public UserVO toVO(UserDTO userDTO) {
UserVO vo = new UserVO();
vo.setUserId(userDTO.getUserId());
vo.setUsername(userDTO.getUsername());
vo.setMaskedEmail(this.maskEmail(userDTO.getEmail()));
vo.setStatusText(this.convertStatus(userDTO.getStatus()));
return vo;
}
private String maskEmail(String email) {
// 脱敏逻辑
return email.replaceAll("(?<=.).(?=.*@)", "*");
}
}4. 统一返回结构
@Data
public class ResultVO<T> {
private int code; // 状态码
private String message; // 提示信息
private T data; // 实际数据(VO/DTO)
public static <T> ResultVO<T> success(T data) {
ResultVO<T> result = new ResultVO<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
}七、总结
| 规则 | DTO | VO |
|---|---|---|
| 核心目标 | 传输效率(减少调用次数) | 展示安全(适配前端+脱敏) |
| 数据来源 | 可能跨多个聚合 | 通常基于单个 DTO |
| 是否脱敏 | ❌ 否 | ✅ 必须脱敏 |
| 是否格式化 | ❌ 原始数据 | ✅ 前端友好格式 |
| 位置 | Service → Controller | Controller → 前端 |
设计原则:先设计 VO 满足前端需求,再反推 DTO 需要聚合哪些数据,最后确定 DO 的数据库设计。
一句话总结:DTO 为服务层效率而生,VO 为前端安全而生。
到此这篇关于Java中DTO和VO区别的文章就介绍到这了,更多相关Java DTO和VO区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
