SpringBoot3 | SpringBoot中Entity、DTO、VO的通俗理解与实战

SpringBoot中Entity、DTO、VO的通俗理解与实战

刚接触SpringBoot开发时,你是不是也有过这样的困惑:明明数据库表对应一个实体类就够了,为什么还要搞出DTO、VO这些"花里胡哨"的东西?直接把数据库实体类传到前端不行吗?

答案是:不行。就像我们寄快递时,不会把家里的保险箱直接寄出去,而是会把里面的东西拿出来装在快递盒里(选需要的东西、去掉敏感信息)。Entity、DTO、VO就是这套"数据包装"的不同容器,各自有明确的使用场景。今天咱们用最直白的案例,把这些概念讲清楚。

一、Entity:和数据库"一对一"的"原始数据袋"

定义:Entity(实体类)是数据库表的"镜像",字段和数据库表一一对应,它的使命就是和数据库打交道------存数据、取数据。

核心作用:作为持久层(Repository层)操作的载体,负责数据在Java对象和数据库表之间的映射。

实战案例:用户表对应的Entity

假设数据库有张user表,结构如下:

字段名 类型 说明
id bigint 主键
username varchar(50) 用户名
password varchar(100) 加密密码
email varchar(100) 邮箱
create_time datetime 创建时间
对应的Entity类:
java 复制代码
import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;

// 与数据库表user映射
@Entity
@Table(name = "user")
@Data // Lombok注解,自动生成getter、setter等
public class UserEntity {
    // 主键
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    // 用户名,对应表中username字段
    private String username;
    
    // 密码(加密存储),对应表中password字段
    private String password;
    
    // 邮箱,对应表中email字段
    private String email;
    
    // 创建时间,对应表中create_time字段
    @Column(name = "create_time")
    private LocalDateTime createTime;
}

注意:Entity只应该在DAO层(数据访问层)和Service层内部使用,绝对不能直接传到前端!因为它包含敏感信息(如password)和前端可能不需要的字段(如createTime)。

二、DTO:服务间数据传输的"快递盒"

定义:DTO(Data Transfer Object,数据传输对象)用于不同服务之间、或服务内部不同层之间传递数据。它就像快递盒,只装对方需要的数据,不多不少。

核心作用:1. 减少数据传输量(只传必要字段);2. 隔离不同服务的实体结构(避免一个服务改实体影响另一个服务)。

实战案例:用户注册的DTO

用户注册时,前端需要传用户名、密码、邮箱,但不需要传id(数据库自增)和createTime(后端生成)。这时候就需要一个UserRegisterDTO

java 复制代码
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Email;

@Data
public class UserRegisterDTO {
    // 用户名不能为空
    @NotBlank(message = "用户名不能为空")
    private String username;
    
    // 密码不能为空
    @NotBlank(message = "密码不能为空")
    private String password;
    
    // 邮箱格式要正确
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
}

Service层接收DTO并转换为Entity:

java 复制代码
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public void register(UserRegisterDTO registerDTO) {
        // 1. DTO转换为Entity
        UserEntity userEntity = new UserEntity();
        userEntity.setUsername(registerDTO.getUsername());
        // 密码加密(实际开发中一定要加密!)
        userEntity.setPassword(encryptPassword(registerDTO.getPassword()));
        userEntity.setEmail(registerDTO.getEmail());
        userEntity.setCreateTime(LocalDateTime.now());
        
        // 2. 保存到数据库
        userRepository.save(userEntity);
    }
    
    // 密码加密方法(示例)
    private String encryptPassword(String rawPassword) {
        return new BCryptPasswordEncoder().encode(rawPassword);
    }
}

三、VO:给前端展示的"最终商品"

定义:VO(View Object,视图对象)是专门给前端页面展示用的数据对象。它就像商店里的商品展示,只展示用户想看到的信息,隐藏内部细节。

核心作用:定制前端需要的展示数据,比如字段别名、组合字段等。

实战案例:用户详情的VO

前端展示用户详情时,需要id、用户名、邮箱,但不需要password和createTime。这时候用UserVO

java 复制代码
import lombok.Data;

@Data
public class UserVO {
    private Long id;
    private String username;
    private String email;
    // 可以增加前端需要的组合字段,比如"用户标签"
    private String userTag;
}

Service层查询Entity并转换为VO:

java 复制代码
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public UserVO getUserDetail(Long userId) {
        // 1. 从数据库查询Entity
        UserEntity userEntity = userRepository.findById(userId)
                .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        // 2. Entity转换为VO
        UserVO userVO = new UserVO();
        userVO.setId(userEntity.getId());
        userVO.setUsername(userEntity.getUsername());
        userVO.setEmail(userEntity.getEmail());
        // 定制前端需要的组合字段
        userVO.setUserTag("普通用户");
        
        return userVO;
    }
}

Controller层返回VO给前端:

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    
    @GetMapping("/{userId}")
    public UserVO getUserDetail(@PathVariable Long userId) {
        return userService.getUserDetail(userId);
    }
}

四、三者转换的"小技巧"

手动转换(setter/getter)虽然直观,但字段多了很麻烦。实际开发中推荐用工具类:

  • ModelMapper:自动映射同名字段,支持自定义映射规则。

  • MapStruct:编译期生成映射代码,性能更好,需要写接口。

ModelMapper示例:

java 复制代码
// 1. 引入依赖
<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>3.2.0</version>
</dependency>

// 2. 配置Bean
@Configuration
public class ModelMapperConfig {
    @Bean
    public ModelMapper modelMapper() {
        return new ModelMapper();
    }
}

// 3. 服务中使用
@Service
public class UserService {
    @Autowired
    private ModelMapper modelMapper;
    
    public UserVO getUserDetail(Long userId) {
        UserEntity userEntity = userRepository.findById(userId).orElseThrow(...);
        // 自动转换Entity到VO
        return modelMapper.map(userEntity, UserVO.class);
    }
}

五、总结:一句话分清三者

对象类型 使用场景 核心目的
Entity DAO层 ↔ 数据库 和数据库表一一对应,负责数据持久化
DTO 服务间/层间数据传输 减少传输量,隔离服务依赖
VO Service层 ↔ 前端 定制前端展示数据,隐藏敏感信息
记住:边界清晰是代码整洁的关键。用对Entity、DTO、VO,能让你的SpringBoot项目结构更清晰,维护性大大提升。
相关推荐
may_一一10 小时前
docker安装的redis状态一直是restarting
java·redis·docker
zhangyifang_00910 小时前
Spring中的SPI机制
java·spring
han_hanker11 小时前
这里使用 extends HashMap<String, Object> 和 类本身定义变量的优缺点
java·开发语言
careathers11 小时前
【JavaSE语法】面向对象初步认识
java·面向对象
coding随想11 小时前
掌控选区的终极武器:getSelection API的深度解析与实战应用
java·前端·javascript
嵌入式小能手11 小时前
飞凌嵌入式ElfBoard-文件I/O的深入学习之存储映射I/O
java·前端·学习
LucianaiB11 小时前
从 0 到 1 玩转 N8N——初识 N8N(入门必看)
后端
ChinaRainbowSea11 小时前
github 仓库主页美化定制
java·后端·github
程序猿小蒜12 小时前
基于springboot的医院资源管理系统开发与设计
java·前端·spring boot·后端·spring