本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net
探索在 Spring Boot 中使用数据传输对象(DTO)的好处,本文包括了手动创建 DTO、使用 ModelMapper 和 Lombok 创建 DTO 的示例。
认真读完本文你会有很大的收获
1. 什么是数据传输对象(DTO)?
数据传输对象(DTO)是一种设计模式,用于封装和传输应用程序不同层之间的数据。
DTO 是轻量级对象,通常只包含必要的字段,不包含任何业务逻辑。DTO 作用于应用程序中不同的业务之间的数据传输,例如在前端和后端之间或在分布式系统中不同的微服务之间。
在 Spring Boot 应用程序中,DTO 特别有用,因为需要在控制器层、服务层和持久层之间传输数据。通过使用 DTO 就可以将内部数据模型与外部表示解耦(这点巨好,老鸟都知道),从而更好地控制数据传输。
2. 在 Spring Boot 中使用 DTO 的好处
在 Spring Boot 应用程序中使用 DTO 有几个优点:
- 数据隔离:DTO 允许将暴露给外部的数据与内部的模型隔离。这可以防止暴露敏感和不必要的数据,并为数据交换提供清晰的字段。
- 减少开销:DTO 可以仅包含特定所需的字段,减少网络传输。这最小化了传输大型对象的开销。
- 版本控制和兼容性:DTO 可以使接口支持向后兼容。一个 API 可以对外提供多个 DTO 结构。
- 提高安全性:通过对 DTO 暴露数据的控制,就避免了数据泄漏以及保护了敏感信息的安全性。
- 增强测试:DTO 简化了单元测试,因为您可以在测试场景中轻松创建和操作它们,而不依赖于复杂的域对象。
3. 在 Spring Boot 中以不同方式使用 DTO
3.1. 手动创建 DTO
在这种方法中,手动创建对实体结构映射的 DTO 类。然后编写代码在实体对象和 DTO 之间映射数据。
public class UserDTO {
private Long id;
private String username;
private String email;
// 构造函数、getter和setter
}
创建一个控制器方法演示手动映射:
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
UserDTO userDTO = new UserDTO();
userDTO.setId(user.getId());
userDTO.setUsername(user.getUsername());
userDTO.setEmail(user.getEmail());
return ResponseEntity.ok(userDTO);
}
}
输出:
调用 / api/users/1 请求时,将收到 UserDTO 结构的用户数据。
{
"id": 1,
"username": "张三",
"email": "zhangsan@qq.com"
}
3.2. 使用 ModelMapper
ModelMapper 用于自动将实体对象映射到 DTO,反之亦然。以下是 pom.xml 依赖:
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.4.3</version>
</dependency>
实例化 ModelMapper 为一个 BEAN,这样就可以在整个程序中使用 ModelMapper
以下是创建 Spring Boot 应用程序中 ModelMapper Bean 的方法:
import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyApplicationConfig {
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}
以下是如何使用 ModelMapper 将实体对象映射到 DTO:
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private ModelMapper modelMapper; // 自动装配ModelMapper Bean
@GetMapping("/{id}")
public ResponseEntity getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
UserDTO userDTO = modelMapper.map(user, UserDTO.class); // 使用ModelMapper进行映射
return ResponseEntity.ok(userDTO);
}
}
输出:
输出将与手动创建 DTO 的示例相同。
3.3 使用 Lombok
在 pom.xml 文件中添加 Lombok 依赖项。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
以下是演示如何使用 Lombok 为 User 实体创建 DTO。
import lombok.Data;
@Data //关键注解
public class UserDTO {
private Long id;
private String username;
private String email;
}
以下是如何使用 Lombok 将实体对象映射到 DTO:
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
UserDTO userDTO = UserDTO.builder()
.id(user.getId())
.username(user.getUsername())
.email(user.getEmail())
.build();
return ResponseEntity.ok(userDTO);
}
}
输出:
输出与手动创建 DTO 的示例相同。
- 在 DTO 中格式化不同类型的值
在 DTO 中格式化不同类型的值是确保数据在序列化或显示时以特定格式呈现的常见要求。根据业务要求格式化的值类型,可以使用各种方法,包括注解、自定义方法或外部库。下面是在 DTO 中格式化不同类型的值实例:
4.1. 格式化日期和时间
4.1.1. 使用 @JsonFormat 注解(Jackson)
要在 DTO 中格式化日期和时间值,可以使用 Jackson 库提供的 @JsonFormat 注解。
import com.fasterxml.jackson.annotation.JsonFormat;
public class UserDTO {
private Long id;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
private Date registrationDate;
// 其他字段、getter和setter
}
在此示例中,registrationDateField 使用 @JsonFormat 注解指定所需的日期和时间格式。
4.1.2. 使用 SimpleDateFormat(自定义方法)
还可以通过在 DTO 类中提供自定义 getter 方法来格式化日期和时间,该方法返回格式化后的日期字符串。
import java.text.SimpleDateFormat;
public class UserDTO {
private Long id;
private Date registrationDate;
public String getFormattedRegistrationDate() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(registrationDate);
}
// 其他字段、getter和setter
}
4.2. 格式化数字
4.2.1. 使用 @NumberFormat 注解(Spring)
要格式化数字值,如数字或货币,您可以使用 Spring 提供的 @NumberFormat 注解。此注解允许您指定数字格式模式。
import org.springframework.format.annotation.NumberFormat;
public class ProductDTO {
private Long id;
@NumberFormat(pattern = "#,###.00")
private BigDecimal price;
// 其他字段、getter和setter
}
在此示例中,price 字段使用 @NumberFormat 注解指定数字格式模式。
4.2.2. 使用 DecimalFormat(自定义方法)
还可以通过在 DTO 类中提供自定义 getter 方法来格式化数字,该方法使用 DecimalFormat 返回格式化后的数字字符串。
import java.text.DecimalFormat;
public class ProductDTO {
private Long id;
private BigDecimal price;
public String getFormattedPrice() {
DecimalFormat df = new DecimalFormat("#,###.00");
return df.format(price);
}
// 其他字段、getter和setter
}
4.3. 格式化字符串
4.3.1. 使用自定义方法
对于格式化字符串值,可以在 DTO 类中创建自定义 getter 方法以根据需要操作字符串。例如,您可以优化空格、大写单词或应用任何其他字符串操作逻辑。
public class ArticleDTO {
private Long id;
private String title;
public String getFormattedTitle() {
// 自定义格式化逻辑在这里
return title.trim(); // 示例:修剪空格
}
// 其他字段、getter和setter
}
4.4. 格式化枚举
4.4.1. 使用自定义方法
在处理 DTO 中的枚举时,可以创建自定义 getter 方法以返回枚举值的格式化表示形式。例如,您可以将枚举值转换为大写或使用不同的表示形式。
public class OrderDTO {
private Long id;
private OrderStatus status;
public String getFormattedStatus() {
return status.toString().toUpperCase(); // 示例:转换为大写
}
// 其他字段、getter和setter
}
4.5. 格式化布尔值
4.5.1. 使用自定义方法
对于布尔值,可以创建自定义 getter 方法以返回格式化表示形式,例如 “是” 或“否”,而不是 “true” 或“false”。
public class UserDTO {
private Long id;
private boolean isActive;
public String getFormattedIsActive() {
return isActive ? "Yes" : "No"; // 示例:转换为"Yes"或"No"
}
// 其他字段、getter和setter
}
通过使用这些方法,可以根据具体要求格式化 DTO 中的不同类型的值,确保在序列化或显示 DTO 时数据以期望的格式呈现。
- 其他注意事项和最佳实践
5.1. DTO 中的验证
在处理 DTO 时,考虑数据验证至关重要。应该验证 DTO 中的传入数据,以确保它满足所需的约束和业务规则。可以使用 Spring 的验证注解(如 @NotNull、@Size)或自定义验证注解来验证 DTO 字段。
以下是使用 Spring 的 @NotBlank 注解进行 DTO 验证的示例:
public class UserDTO {
@NotNull
private Long id;
@NotBlank
@Size(min = 5, max = 50)
private String username;
@Email
private String email;
// 构造函数、getter和setter
}
5.2. 用于复杂嵌套对象的 DTO
在实际业务中,处理嵌套对象的 DTO 会很复杂。需要创建嵌套的 DTO 来准确映射这些结构。
例如,如果一个 User 与一组 Address 对象关联,可以创建一个嵌套 AddressDTO 的 UserDTO:
public class UserDTO {
private Long id;
private String username;
private String email;
private List<AddressDTO> addresses;
// 构造函数、getter和setter
}
5.3. DTO 版本控制
随着业务的发展,可能需要对 DTO 进行更改。为了保持向后兼容性则需要考虑对 DTO 进行版本控制。例如可以通过向 DTO 添加版本标识符或在必要时创建新的 DTO 版本来实现。
5.4. RESTful API 中的 DTO
DTO 在 RESTful API 中常用于表示客户端和服务器之间的数据交换。在设计 RESTful 端时,应仔细选择或创建 DTO,以满足不同业务需求。
- 将 DTO 与 Spring 验证一起使用
Spring 提供了一个强大的机制,使用 @Valid 注解在控制器方法中去验证 DTO。当你将 DTO 参数与 @Valid 注解一起使用时,Spring 将自动基于 DTO 类中定义的验证规则触发验证。
以下是使用 DTO 验证的控制器方法示例:
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/create")
public ResponseEntity createUser(@Valid @RequestBody UserDTO userDTO) {
// 验证逻辑会自动触发
// 将UserDTO映射到User实体并保存它
User user = modelMapper.map(userDTO, User.class);
User savedUser = userService.saveUser(user);
// 返回保存的UserDTO
UserDTO savedUserDTO = modelMapper.map(savedUser, UserDTO.class);
return ResponseEntity.status(HttpStatus.CREATED).body(savedUserDTO);
}
}
在此示例中,@Valid 注解基于 UserDTO 类中定义的规则触发验证。如果验证失败,Spring 将自动处理验证错误并返回带有适当错误消息的响应数据。
- 微服务架构中的 DTO
在微服务架构中,DTO 在定义微服务之间的边界方面起着关键作用。每个微服务都可以拥有一套针对其特定需求而量身定制的 DTO。这种分离模式确保了微服务之间的松耦合。
DTO 还有助于减少微服务之间传输的数据量,这对于维护基于微服务的系统的性能和可扩展性至关重要。
- 结论
数据传输对象(DTO)在 Spring Boot 应用程序中不可或缺,它充当应用程序不同层和外部系统之间的桥梁。通过仔细设计和使用 DTO,可以改善数据隔离、减少开销、增强安全性并简化测试。无论是选择手动创建 DTO 或使用像 ModelMapper 这样的库,还是利用 Lombok 减少代码,关键是选择最适合项目要求和可维护性的方法。将 DTO 作为架构一部分,你将会更好地打造自己健壮高效的 Spring Boot 应用程序。