Java中DTO和VO的区别举例详解

 更新时间:2025年12月25日 10:53:33   作者:趁你还年轻_  
在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,仅在字段差异大时创建新类

五、与其他对象的关系

在分层架构中,完整的对象体系如下:

完整对象体系

缩写全称作用位置示例
DOData Object持久层 ↔ 数据库UserDO(对应数据库表)
DTOData Transfer ObjectService ↔ ControllerUserOrderDTO(聚合数据)
VOView ObjectController ↔ 前端UserOrderVO(脱敏+格式化)
BOBusiness ObjectService 层内部OrderBO(处理业务逻辑)
POPersistant Object同 DO(老叫法)UserPO
AOApplication 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;
    }
}

七、总结

规则DTOVO
核心目标传输效率(减少调用次数)展示安全(适配前端+脱敏)
数据来源可能跨多个聚合通常基于单个 DTO
是否脱敏❌ 否✅ 必须脱敏
是否格式化❌ 原始数据✅ 前端友好格式
位置Service → ControllerController → 前端

设计原则先设计 VO 满足前端需求,再反推 DTO 需要聚合哪些数据,最后确定 DO 的数据库设计。

一句话总结DTO 为服务层效率而生,VO 为前端安全而生

到此这篇关于Java中DTO和VO区别的文章就介绍到这了,更多相关Java DTO和VO区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MySQL如何设置自动增长序列SEQUENCE的方法

    MySQL如何设置自动增长序列SEQUENCE的方法

    本文主要介绍了MySQL如何设置自动增长序列SEQUENCE的方法,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • java中的引用类型之强软弱虚详解

    java中的引用类型之强软弱虚详解

    这篇文章主要给大家介绍了关于java中引用类型之强软弱虚的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用java具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2020-06-06
  • 浅谈java调用Restful API接口的方式

    浅谈java调用Restful API接口的方式

    这篇文章主要介绍了浅谈java调用Restful API接口的方式,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • 基于springboot微信公众号开发(微信自动回复)

    基于springboot微信公众号开发(微信自动回复)

    这篇文章主要介绍了基于springboot微信公众号开发(微信自动回复),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11
  • Java并发编程变量可见性避免指令重排使用详解

    Java并发编程变量可见性避免指令重排使用详解

    这篇文章主要为大家介绍了Java并发编程变量可见性避免指令重排使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • Hibernate实现批量添加数据的方法

    Hibernate实现批量添加数据的方法

    这篇文章主要介绍了Hibernate实现批量添加数据的方法,详细分析了基于Hibernate执行批量添加操作的具体步骤与相关实现代码,需要的朋友可以参考下
    2016-03-03
  • 通过Java实现获取表的自增主键值

    通过Java实现获取表的自增主键值

    这篇文章主要为大家详细介绍了如何通过Java实现获取表的自增主键值,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的可以了解一下
    2023-06-06
  • Java反射机制(Reflection)浅析

    Java反射机制(Reflection)浅析

    这篇文章主要介绍了Java反射机制(Reflection)浅析,本文以实例讲解Java的反射机制,需要的朋友可以参考下
    2014-07-07
  • Java开发中解决Js的跨域问题过程解析

    Java开发中解决Js的跨域问题过程解析

    这篇文章主要介绍了Java开发中解决Js的跨域问题过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Java动态字节码注入技术的实现

    Java动态字节码注入技术的实现

    Java动态字节码注入技术是一种在运行时修改Java字节码的技术,本文主要介绍了Java动态字节码注入技术的实现,具有一定的参考价值,感兴趣的可以了解一下
    2023-08-08

最新评论