实体类设计规范

发布于:2025-04-06 ⋅ 阅读:(68) ⋅ 点赞:(0)

实体类设计规范

目录

  1. 概述
  2. 三种实体类的定义与特点
  3. 使用场景
  4. 代码示例
  5. 最佳实践
  6. 常见问题

概述

在Java后端开发中,我们通常会使用三种不同类型的实体类:Domain(领域模型)、DTO(数据传输对象)和VO(视图对象)。这三种实体类各自有不同的用途和特点,合理使用它们可以使代码结构更清晰,职责更分明,提高代码的可维护性和可扩展性。

三种实体类的定义与特点

1. Domain(领域模型)

定义:Domain是业务领域中的核心实体,代表系统中的业务对象。

特点

  • 包含完整的业务属性和行为
  • 与数据库表结构一一对应
  • 通常包含业务逻辑方法
  • 生命周期较长,贯穿整个业务过程
  • 通常使用JPA或MyBatis等ORM框架进行映射

示例

@Data
@TableName("user")
public class User {
    
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String username;
    
    private String password;
    
    private String email;
    
    private String phone;
    
    private Integer status;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    
    // 业务方法
    public boolean isActive() {
        return status == 1;
    }
    
    public void activate() {
        this.status = 1;
    }
    
    public void deactivate() {
        this.status = 0;
    }
}

2. DTO(数据传输对象)

定义:DTO是用于在不同层之间传输数据的对象,特别是在控制器层和服务层之间。

特点

  • 只包含需要传输的数据字段
  • 通常不包含业务逻辑
  • 可以组合多个Domain对象的属性
  • 生命周期较短,仅用于数据传输
  • 通常包含数据验证注解
  • 可以隐藏敏感信息

示例

@Data
public class UserDTO {
    
    private Long id;
    
    @NotBlank(message = "用户名不能为空")
    private String username;
    
    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 20, message = "密码长度必须在6-20位之间")
    private String password;
    
    @Email(message = "邮箱格式不正确")
    private String email;
    
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;
}

3. VO(视图对象)

定义:VO是专门为视图层设计的对象,用于向前端展示数据。

特点

  • 只包含需要展示给用户的字段
  • 可以组合多个Domain对象的属性
  • 通常包含格式化后的数据
  • 生命周期最短,仅用于数据展示
  • 不包含业务逻辑
  • 可以包含计算属性

示例

@Data
public class UserVO {
    
    private Long id;
    
    private String username;
    
    private String email;
    
    private String phone;
    
    private String statusText; // 格式化后的状态文本
    
    private String createTimeStr; // 格式化后的创建时间
    
    // 可以包含一些计算属性
    public String getDisplayName() {
        return username + " (" + email + ")";
    }
}

使用场景

Domain使用场景

  1. 数据库映射:与数据库表结构一一对应,用于ORM框架映射
  2. 业务逻辑处理:包含业务规则和行为方法
  3. 领域驱动设计:在DDD中作为核心实体
  4. 服务层内部使用:在服务层内部传递数据和处理业务逻辑

DTO使用场景

  1. 接收前端请求参数:在控制器层接收前端传来的数据
  2. 服务间数据传输:在不同服务之间传输数据
  3. 数据验证:使用注解进行数据验证
  4. 隐藏敏感信息:不包含敏感字段,如密码
  5. 组合多个实体数据:将多个实体的数据组合在一起

VO使用场景

  1. 返回给前端的数据:在控制器层返回给前端的数据
  2. 数据展示格式化:将原始数据格式化为适合展示的形式
  3. 组合多个实体的数据:将多个实体的数据组合在一起展示
  4. 隐藏敏感信息:不包含敏感字段,如密码
  5. 包含计算属性:包含一些需要计算或转换的属性

代码示例

实体类转换

// 使用BeanUtils进行转换
public class UserConverter {
    
    // Domain转DTO
    public static UserDTO toDTO(User user) {
        UserDTO dto = new UserDTO();
        BeanUtils.copyProperties(user, dto);
        return dto;
    }
    
    // Domain转VO
    public static UserVO toVO(User user) {
        UserVO vo = new UserVO();
        BeanUtils.copyProperties(user, vo);
        // 格式化数据
        vo.setStatusText(user.getStatus() == 1 ? "启用" : "禁用");
        vo.setCreateTimeStr(DateUtils.format(user.getCreateTime(), "yyyy-MM-dd HH:mm:ss"));
        return vo;
    }
    
    // DTO转Domain
    public static User toDomain(UserDTO dto) {
        User user = new User();
        BeanUtils.copyProperties(dto, user);
        return user;
    }
}

控制器中使用

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/{id}")
    public Result<UserVO> getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        if (user == null) {
            throw new BusinessException(ResultCode.USER_NOT_EXIST);
        }
        return Result.success(UserConverter.toVO(user));
    }
    
    @PostMapping
    public Result<UserVO> createUser(@Valid @RequestBody UserDTO userDTO) {
        User user = UserConverter.toDomain(userDTO);
        User createdUser = userService.createUser(user);
        return Result.success(UserConverter.toVO(createdUser));
    }
}

服务层中使用

@Service
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Override
    public User getUserById(Long id) {
        return userRepository.selectById(id);
    }
    
    @Override
    public PageResult<User> getUserPage(Integer pageNum, Integer pageSize) {
        Page<User> page = new Page<>(pageNum, pageSize);
        Page<User> userPage = userRepository.selectPage(page, null);
        
        return new PageResult<>(
            (int)userPage.getCurrent(),
            (int)userPage.getSize(),
            userPage.getTotal(),
            userPage.getRecords()
        );
    }
    
    @Override
    public User createUser(User user) {
        // 业务逻辑处理
        userRepository.insert(user);
        return user;
    }
}

最佳实践

1. 命名规范

  • Domain类:直接使用业务实体名称,如UserOrderProduct
  • DTO类:在实体名称后加上DTO后缀,如UserDTOOrderDTOProductDTO
  • VO类:在实体名称后加上VO后缀,如UserVOOrderVOProductVO

2. 包结构

com.example.project
├── entity
│   ├── domain             # 领域模型
│   │   ├── User.java
│   │   ├── Order.java
│   │   └── Product.java
│   ├── dto                # 数据传输对象
│   │   ├── UserDTO.java
│   │   ├── OrderDTO.java
│   │   └── ProductDTO.java
│   └── vo                 # 视图对象
│       ├── UserVO.java
│       ├── OrderVO.java
│       └── ProductVO.java

3. 转换工具

  • 使用BeanUtilsMapStruct等工具简化转换过程
  • 对于复杂转换,可以创建专门的转换器类
  • 考虑使用@Mapper注解(MapStruct)自动生成转换代码

4. 性能考虑

  • 避免过度转换,只在必要时进行实体类转换
  • 对于大量数据的转换,考虑使用批量转换
  • 使用缓存减少重复转换

5. 安全性考虑

  • 在DTO和VO中不包含敏感信息,如密码
  • 在转换过程中过滤掉不需要的字段
  • 使用@JsonIgnore注解忽略敏感字段的序列化

常见问题

1. 何时使用DTO和VO?

  • 当需要接收前端请求参数时,使用DTO
  • 当需要返回数据给前端时,使用VO
  • 当需要在不同服务之间传输数据时,使用DTO

2. 如何处理复杂对象?

  • 对于复杂对象,可以创建专门的转换器类
  • 使用MapStruct等工具自动生成转换代码
  • 对于嵌套对象,考虑使用递归转换

3. 如何处理集合转换?

  • 使用Stream API进行集合转换
  • 创建专门的集合转换方法
  • 考虑使用批量转换提高性能
// 集合转换示例
public static List<UserVO> toVOList(List<User> users) {
    return users.stream()
            .map(UserConverter::toVO)
            .collect(Collectors.toList());
}

4. 如何处理继承关系?

  • 对于有继承关系的实体,考虑使用泛型转换方法
  • 使用反射处理继承关系
  • 对于复杂继承,考虑使用策略模式

5. 如何处理循环引用?

  • 使用@JsonIgnore注解忽略循环引用
  • 使用@JsonManagedReference@JsonBackReference处理双向关系
  • 考虑使用DTO和VO打破循环引用

网站公告

今日签到

点亮在社区的每一天
去签到