Spring Boot中的数据传输对象(DTO)
探索在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依赖:
org.modelmapper modelmapper 2.4.3
实例化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依赖项。
org.projectlombok lombok 1.18.22 provided
以下是演示如何使用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的示例相同。
4.在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.其他注意事项和最佳实践
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 addresses; // 构造函数、getter和setter }
5.3. DTO版本控制
随着业务的发展,可能需要对DTO进行更改。为了保持向后兼容性则需要考虑对DTO进行版本控制。例如可以通过向DTO添加版本标识符或在必要时创建新的DTO版本来实现。
5.4. RESTful API中的DTO
DTO在RESTful API中常用于表示客户端和服务器之间的数据交换。在设计RESTful端时,应仔细选择或创建DTO,以满足不同业务需求。
6.将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将自动处理验证错误并返回带有适当错误消息的响应数据。
7.微服务架构中的DTO
在微服务架构中,DTO在定义微服务之间的边界方面起着关键作用。每个微服务都可以拥有一套针对其特定需求而量身定制的DTO。这种分离模式确保了微服务之间的松耦合。
DTO还有助于减少微服务之间传输的数据量,这对于维护基于微服务的系统的性能和可扩展性至关重要。
8.结论
数据传输对象(DTO)在Spring Boot应用程序中不可或缺,它充当应用程序不同层和外部系统之间的桥梁。通过仔细设计和使用DTO,可以改善数据隔离、减少开销、增强安全性并简化测试。无论是选择手动创建DTO或使用像ModelMapper这样的库,还是利用Lombok减少代码,关键是选择最适合项目要求和可维护性的方法。将DTO作为架构一部分,你将会更好地打造自己健壮高效的Spring Boot应用程序。