6.7 KiB
6.7 KiB
User对象变动时的修改最佳实践
概述
当User
实体类需要变动时(如添加字段、删除字段、修改字段类型等),本文档提供了最小化影响的修改策略。
核心原则
1. 分层隔离原则
- 控制器层:只依赖DTO,不直接使用实体
- 服务层:处理业务逻辑,可以使用实体
- 数据层:直接操作实体
2. 单一职责原则
- 每个组件只负责一个职责
- 变更影响范围最小化
变更场景与应对策略
场景1:User实体添加新字段
示例:为User添加middleName
字段
步骤1:修改User实体
// User.java
public class User {
// 现有字段...
private String middleName; // 新增字段
// getter/setter
public String getMiddleName() { return middleName; }
public void setMiddleName(String middleName) { this.middleName = middleName; }
}
步骤2:更新UserDTO(可选)
// UserDTO.java
public class UserDTO {
// 现有字段...
private String middleName; // 根据需要添加
// getter/setter
}
步骤3:更新UserMapper
// UserMapper.java
public UserDTO toDTO(User user) {
// 现有映射...
dto.setMiddleName(user.getMiddleName()); // 新增映射
return dto;
}
public User toEntity(UserDTO dto) {
// 现有映射...
user.setMiddleName(dto.getMiddleName()); // 新增映射
return user;
}
步骤4:更新控制器(如果需要暴露新字段)
// UserControllerV2.java
public static class CreateUserRequest {
// 现有字段...
private String middleName; // 根据需要添加
// getter/setter
}
影响范围:
- ✅ 控制器层:无需修改(除非要暴露新字段)
- ✅ 服务层:无需修改
- ⚠️ 映射层:需要更新UserMapper
- ⚠️ 实体层:添加新字段
场景2:User实体删除字段
示例:删除User的fax
字段
步骤1:标记字段为废弃(渐进式删除)
// User.java
public class User {
@Deprecated
private String fax; // 标记为废弃,暂不删除
@Deprecated
public String getFax() { return fax; }
@Deprecated
public void setFax(String fax) { this.fax = fax; }
}
步骤2:从DTO中移除字段
// UserDTO.java - 不再包含fax字段
步骤3:更新UserMapper
// UserMapper.java
public UserDTO toDTO(User user) {
// 移除fax相关映射
// dto.setFax(user.getFax()); // 注释或删除
return dto;
}
步骤4:数据库迁移(如果需要)
-- 可选:删除数据库列
ALTER TABLE users DROP COLUMN fax;
场景3:User实体字段类型变更
示例:将salary
从Double
改为BigDecimal
步骤1:创建新字段,保留旧字段
// User.java
public class User {
@Deprecated
private Double salary; // 旧字段,标记废弃
private BigDecimal salaryAmount; // 新字段
// 兼容性方法
public Double getSalary() {
return salaryAmount != null ? salaryAmount.doubleValue() : salary;
}
public void setSalary(Double salary) {
this.salary = salary;
this.salaryAmount = salary != null ? BigDecimal.valueOf(salary) : null;
}
public BigDecimal getSalaryAmount() { return salaryAmount; }
public void setSalaryAmount(BigDecimal salaryAmount) {
this.salaryAmount = salaryAmount;
this.salary = salaryAmount != null ? salaryAmount.doubleValue() : null;
}
}
步骤2:更新UserMapper
// UserMapper.java
public UserDTO toDTO(User user) {
// 使用新的getter方法
dto.setSalary(user.getSalary()); // 自动处理类型转换
return dto;
}
最佳实践总结
1. 使用DTO模式
// ❌ 错误:控制器直接返回实体
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUserById(id));
}
// ✅ 正确:控制器返回DTO
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
UserDTO dto = userMapper.toDTO(user);
return ResponseEntity.ok(dto);
}
2. 使用Mapper进行转换
// ❌ 错误:在控制器中手动转换
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setName(user.getName());
// ... 大量重复代码
return ResponseEntity.ok(dto);
}
// ✅ 正确:使用专门的Mapper
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
UserDTO dto = userMapper.toDTO(user); // 集中处理转换逻辑
return ResponseEntity.ok(dto);
}
3. 渐进式变更
// ✅ 渐进式删除字段
public class User {
@Deprecated // 第一步:标记废弃
private String oldField;
private String newField; // 第二步:添加新字段
// 第三步:提供兼容性方法
public String getOldField() {
return newField; // 重定向到新字段
}
}
4. 版本化API
// 为重大变更提供版本化API
@RestController
@RequestMapping("/api/v1/users") // 旧版本
public class UserControllerV1 { ... }
@RestController
@RequestMapping("/api/v2/users") // 新版本
public class UserControllerV2 { ... }
变更检查清单
添加字段时
- 更新User实体
- 考虑是否需要更新UserDTO
- 更新UserMapper的映射方法
- 更新相关的请求/响应DTO
- 添加数据库迁移脚本
- 更新单元测试
删除字段时
- 标记字段为@Deprecated
- 从UserDTO中移除字段
- 更新UserMapper移除相关映射
- 更新API文档
- 通知API使用者
- 计划最终删除时间
修改字段类型时
- 创建新字段,保留旧字段
- 提供兼容性方法
- 更新UserMapper处理类型转换
- 添加数据迁移逻辑
- 测试向后兼容性
- 计划旧字段删除时间
工具推荐
1. MapStruct(自动映射)
@Mapper(componentModel = "spring")
public interface UserMapper {
UserDTO toDTO(User user);
User toEntity(UserDTO dto);
}
2. 数据库迁移工具
- Flyway
- Liquibase
3. API版本管理
- Spring Boot Actuator
- Swagger/OpenAPI
总结
通过采用DTO模式、Mapper模式和渐进式变更策略,可以最大程度地减少User实体变更对系统的影响。关键是要保持各层之间的松耦合,并建立清晰的变更流程。