SpringBoot中的数据传输对象(DTO)

1.什么是数据传输对象(DTO)?

数据传输对象(DTO)是一种设计模式,用于在应用程序的不同层之间封装和传输数据。DTO是轻量级对象,通常只包含必要的字段,不包括任何业务逻辑。它们作为一种数据结构在应用程序的不同部分之间传输数据,例如前端和后端之间或分布式系统中不同微服务之间。

DTO在SpringBoot应用程序中特别有用,因为数据需要在控制器层、服务层和持久层之间传输。通过使用DTO,可以将内部数据模型与外部表示分离,从而更好地控制数据传输。

2.在SpringBoot中使用DTO的好处

在SpringBoot应用程序中使用DTO提供了几个优点:

  1. 数据隔离:DTO允许您将暴露给外部世界的数据与内部域模型隔离。这可以防止暴露敏感或不必要的数据,并为数据交换提供明确的合同。
  2. 降低开销:DTO可以仅包含特定用例所需的字段,从而减少通过网络传输的数据量。这样可以最小化与传输大型对象相关的开销。
  3. 版本控制和兼容性:DTO使管理版本控制和确保向后兼容性变得更容易。您可以独立于域模型来改进DTO,从而更容易处理API中的更改。
  4. 更好的安全性:通过控制通过DTO公开的数据,您可以通过避免数据泄漏和限制对敏感信息的访问来增强安全性。
  5. 增强的测试:DTO简化了单元测试,因为您可以在测试场景中轻松创建和操作它们,而无需依赖复杂的域对象。

3.在SpringBoot中使用DTO的不同方法

3.1.手动创建DTO

在这种方法中,您可以手动创建DTO类,这些DTO类反映了域实体的结构。然后编写代码在域对象和DTO之间映射数据。

arduino 复制代码
public class UserDTO {
    private Long id;
    private String username;
    private String email;

// Constructors, getters, and setters

}

现在,让我们创建一个控制器方法来演示手动映射:

less 复制代码
@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发送GET请求时,您将收到包含用户数据的JSON响应。

perl 复制代码
{
    "id": 1,
    "username": "john_doe",
    "email": "john.doe@example.com"
}

3.2.使用ModelMapper

ModelMapper是一个流行的库,用于自动化域对象到DTO的映射,反之亦然。要在SpringBoot项目中使用ModelMapper,您需要将以下依赖项添加到pom.xml:

xml 复制代码
<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>2.4.3</version>
</dependency>

要将ModelMapper用作Springbean,可以在配置类或主应用程序类中创建bean定义。这允许您在整个应用程序中集中配置和使用ModelMapper。

以下是如何在SpringBoot应用程序中创建ModelMapper bean:

kotlin 复制代码
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:

less 复制代码
@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;
    
    @Autowired
    private ModelMapper modelMapper; // Autowire the ModelMapper bean
    
    @GetMapping("/{id}")
    public ResponseEntity getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        
        UserDTO userDTO = modelMapper.map(user, UserDTO.class); // Use ModelMapper for mapping
        
        return ResponseEntity.ok(userDTO);
    }
}

输出量:

输出将与手动DTO创建示例中的输出相同。

3.3.使用Lombok

在pom.xml文件中添加Lombok依赖项。

xml 复制代码
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version> 
    <scope>provided</scope>
</dependency>

现在,让我们演示如何使用Lombok为我们的User实体创建DTO。

kotlin 复制代码
import lombok.Data;

@Data
public class UserDTO {
    private Long id;
    private String username;
    private String email;
}

以下是如何使用Lombok将域对象映射到DTO:

less 复制代码
@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注释(杰克逊)

要格式化DTO中的日期和时间值,可以使用杰克逊库提供的@JsonFormat注释,该注释通常用于JSON序列化。

kotlin 复制代码
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;
    
    // Other fields, getters, and setters
}

在本例中,registrationDate字段使用@JsonFormat进行注释,以指定所需的日期和时间格式。

4.1.2.使用SimpleDateFormat(自定义方法)

还可以通过在DTO类中提供自定义getter方法来格式化日期和时间,该方法将格式化的日期作为字符串返回。

java 复制代码
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);
    }
    
    // Other fields, getters, and setters
}

4.2.设置数字格式

4.2.1.使用@NumberFormat注释(Spring)

要格式化数值,如数字或货币,您可以使用Spring提供的@NumberFormat annotation。此注释允许您指定数字格式设置模式。

kotlin 复制代码
import org.springframework.format.annotation.NumberFormat;

public class ProductDTO {
    private Long id;
    
    @NumberFormat(pattern = "#,###.00")
    private BigDecimal price;
    
    // Other fields, getters, and setters
}

在本例中,price字段使用@NumberFormat进行注释,以指定数字格式设置模式。

4.2.2.使用DecimalFormat(自定义方法)

还可以通过在DTO类中提供自定义getter方法来格式化数字,该方法使用DecimalFormat将格式化的数字作为字符串返回。

java 复制代码
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);
    }
    
    // Other fields, getters, and setters
}

4.3.格式化字符串

4.3.1.使用自定义方法

为了格式化字符串值,可以在DTO类中创建自定义getter方法,以根据需要操作字符串。例如,您可以修剪空白、大写单词或应用任何其他字符串操作逻辑。

typescript 复制代码
public class ArticleDTO {
    private Long id;
    private String title;
    
    public String getFormattedTitle() {
        // Custom formatting logic here
        return title.trim(); // Example: Trim whitespace
    }
    
    // Other fields, getters, and setters
}

4.4.目录枚举

4.4.1.使用自定义方法

在DTO中处理枚举时,可以创建自定义getter方法来返回枚举值的格式化表示。例如,您可以将枚举值转换为NULL或使用不同的表示形式。

kotlin 复制代码
public class OrderDTO {
    private Long id;
    private OrderStatus status;
    
    public String getFormattedStatus() {
        return status.toString().toUpperCase(); // Example: Convert to uppercase
    }
    
    // Other fields, getters, and setters
}

4.5.布尔型

4.5.1.使用自定义方法

对于布尔值,您可以创建自定义getter方法来返回格式化的表示,例如"Yes"或"No",而不是"true"或"false"。

typescript 复制代码
public class UserDTO {
    private Long id;
    private boolean isActive;
    
    public String getFormattedIsActive() {
        return isActive ? "Yes" : "No"; // Example: Convert to "Yes" or "No"
    }
    
    // Other fields, getters, and setters
}

通过使用这些方法,您可以根据特定要求在DTO中设置不同类型的值的格式,从而确保在序列化或显示DTO时以所需格式显示数据。

5.其他注意事项和最佳实践

5.1. DTO中的验证

在处理DTO时,必须考虑数据验证。您应该验证DTO中的传入数据,以确保它满足所需的约束和业务规则。您可以使用Spring的validation annotations(如@NotNull@Size)或自定义validation annotations来验证DTO字段。

下面是使用Spring的@NotBlank annotation进行DTO验证的示例:

less 复制代码
public class UserDTO {
    @NotNull
    private Long id;
    
    @NotBlank
    @Size(min = 5, max = 50)
    private String username;
    
    @Email
    private String email;
    
    // Constructors, getters, and setters
}

5.2.复杂嵌套对象的DTO

在实际应用程序中,DTO在处理嵌套对象或关系时可能会变得更加复杂。您可能需要创建嵌套DTO以准确表示这些结构。

例如,如果用户有一个与之关联的Address对象列表,则可以创建一个具有嵌套AddressDTO的UserDTO:

arduino 复制代码
public class UserDTO {
    private Long id;
    private String username;
    private String email;
    private List addresses;
    
    // Constructors, getters, and setters
}

5.3. DTO版本控制

随着应用程序的发展,可能需要对DTO进行更改。要保持向后兼容性,请考虑对DTO进行版本控制。可以通过向DTO类添加版本标识符或在必要时创建新的DTO版本来实现这一点。

5.4. RESTful API中的DTO

在RESTful API中,DTO通常用于表示客户端和服务器之间交换的数据。在设计RESTful端点时,您应该仔细选择和构建DTO,以匹配特定的用例和客户端需求。这确保了高效的数据传输和清晰的API合同。

6.使用DTO和Spring验证

Spring提供了一个强大的机制,可以在控制器方法中使用@Valid annotation来验证DTO。当您使用@Valid注释DTO参数时,Spring将根据DTO类中定义的验证约束自动触发验证。

下面是一个在控制器方法中使用DTO验证的示例:

less 复制代码
@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;
    
    @PostMapping("/create")
    public ResponseEntity createUser(@Valid @RequestBody UserDTO userDTO) {
        // Your validation logic is automatically triggered
        
        // Map UserDTO to User entity and save it
        User user = modelMapper.map(userDTO, User.class);
        User savedUser = userService.saveUser(user);
        
        // Return the saved 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)在SpringBoot应用程序中不可或缺,它是应用程序的不同层与外部系统之间的桥梁。通过仔细设计和使用DTO,可以改进数据隔离、减少开销、增强安全性并简化测试。无论您选择手动创建DTO,使用ModelMapper等库,还是利用Lombok来减少代码,关键是选择最适合项目需求和可维护性的方法。有了DTO作为架构的一个组成部分,您就可以更好地构建健壮高效的SpringBoot应用程序。

相关推荐
goTsHgo10 分钟前
在 Spring Boot 的 MVC 框架中 路径匹配的实现 详解
spring boot·后端·mvc
waicsdn_haha22 分钟前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
Q_192849990632 分钟前
基于Spring Boot的摄影器材租赁回收系统
java·spring boot·后端
良许Linux36 分钟前
0.96寸OLED显示屏详解
linux·服务器·后端·互联网
求知若饥1 小时前
NestJS 项目实战-权限管理系统开发(六)
后端·node.js·nestjs
左羊1 小时前
【代码备忘录】复杂SQL写法案例(一)
后端
gb42152871 小时前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶1 小时前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
颜淡慕潇2 小时前
【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
后端·云原生·容器·kubernetes