Java 中的数据传输对象(Data Transfer Object)
简介
在 Java 开发中,数据传输对象(Data Transfer Object,简称 DTO)是一种设计模式,它用于在不同的层(如表示层、业务逻辑层和数据访问层)之间传输数据。其主要目的是减少不同层之间的耦合,提高代码的可维护性和可扩展性。通过使用 DTO,可以将相关的数据封装在一起,以一种统一、简洁的方式在各层之间传递,避免了在方法参数中传递大量分散的数据。
目录
- 基础概念
- 使用方法
- 创建 DTO 类
- 在不同层之间传递 DTO
- 常见实践
- 与数据库交互时使用 DTO
- 与 RESTful API 结合使用 DTO
- 最佳实践
- DTO 的设计原则
- 处理复杂对象结构
- 数据验证
- 小结
- 参考资料
基础概念
数据传输对象本质上是一个简单的 JavaBean,它包含了一系列的私有属性以及对应的 get 和 set 方法。这些属性用于存储需要在不同层之间传输的数据。通常,DTO 不包含任何业务逻辑,只负责数据的存储和传递。
例如,我们有一个用户信息系统,需要在不同层之间传递用户的基本信息,如姓名、年龄和邮箱。我们可以创建一个 UserDTO
类:
public class UserDTO {
private String name;
private int age;
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
这个 UserDTO
类就是一个典型的数据传输对象,它封装了用户的相关信息,其他层可以通过 get 和 set 方法来访问和修改这些数据。
使用方法
创建 DTO 类
创建 DTO 类的步骤如下: 1. 定义类名,通常以 DTO 结尾,以便于识别。 2. 声明私有属性,用于存储需要传输的数据。 3. 为每个属性生成 get 和 set 方法。
例如,我们创建一个用于传输订单信息的 OrderDTO
类:
public class OrderDTO {
private long orderId;
private String productName;
private int quantity;
private double totalPrice;
public long getOrderId() {
return orderId;
}
public void setOrderId(long orderId) {
this.orderId = orderId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public double getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(double totalPrice) {
this.totalPrice = totalPrice;
}
}
在不同层之间传递 DTO
假设我们有一个三层架构:表示层(Servlet 或 Spring Controller)、业务逻辑层(Service)和数据访问层(DAO)。
在业务逻辑层的 Service 方法中,我们可以从数据访问层获取数据,并将其转换为 DTO 后返回给表示层:
import java.util.List;
public class OrderService {
private OrderDAO orderDAO;
public OrderService(OrderDAO orderDAO) {
this.orderDAO = orderDAO;
}
public List<OrderDTO> getAllOrders() {
List<Order> orders = orderDAO.getAllOrders();
List<OrderDTO> orderDTOs = new ArrayList<>();
for (Order order : orders) {
OrderDTO orderDTO = new OrderDTO();
orderDTO.setOrderId(order.getOrderId());
orderDTO.setProductName(order.getProductName());
orderDTO.setQuantity(order.getQuantity());
orderDTO.setTotalPrice(order.getTotalPrice());
orderDTOs.add(orderDTO);
}
return orderDTOs;
}
}
在表示层(如 Spring Controller)中,我们可以接收这个 DTO 并将其返回给客户端:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class OrderController {
private OrderService orderService;
@Autowired
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/orders")
public List<OrderDTO> getOrders() {
return orderService.getAllOrders();
}
}
常见实践
与数据库交互时使用 DTO
在与数据库交互时,通常会从数据库中查询数据并封装到实体类(如 Hibernate 实体)中。然后,我们可以将实体类转换为 DTO 并传递给其他层。这样可以避免将数据库相关的信息暴露给其他层,同时也可以对数据进行必要的转换和过滤。
例如,我们有一个 User
实体类和对应的 UserDTO
:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private int age;
private String email;
// getters and setters
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
public class UserDTO {
private long id;
private String name;
private int age;
private String email;
// getters and setters
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
在数据访问层(DAO)中查询数据并转换为 DTO:
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import java.util.stream.Collectors;
public class UserDAO {
@PersistenceContext
private EntityManager entityManager;
public List<UserDTO> getAllUsers() {
String jpql = "SELECT u FROM User u";
List<User> users = entityManager.createQuery(jpql, User.class).getResultList();
return users.stream()
.map(user -> {
UserDTO userDTO = new UserDTO();
userDTO.setId(user.getId());
userDTO.setName(user.getName());
userDTO.setAge(user.getAge());
userDTO.setEmail(user.getEmail());
return userDTO;
})
.collect(Collectors.toList());
}
}
与 RESTful API 结合使用 DTO
在开发 RESTful API 时,DTO 可以用于定义 API 的输入和输出数据格式。通过使用 DTO,可以将 API 的输入输出与内部业务逻辑解耦,提高 API 的灵活性和可维护性。
例如,我们有一个创建用户的 API,输入数据使用 CreateUserDTO
:
public class CreateUserDTO {
private String name;
private int age;
private String email;
// getters and setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
在 Spring Controller 中处理这个 API:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/users")
public UserDTO createUser(@RequestBody CreateUserDTO createUserDTO) {
User user = new User();
user.setName(createUserDTO.getName());
user.setAge(createUserDTO.getAge());
user.setEmail(createUserDTO.getEmail());
User savedUser = userService.saveUser(user);
UserDTO userDTO = new UserDTO();
userDTO.setId(savedUser.getId());
userDTO.setName(savedUser.getName());
userDTO.setAge(savedUser.getAge());
userDTO.setEmail(savedUser.getEmail());
return userDTO;
}
}
最佳实践
DTO 的设计原则
- 单一职责原则:每个 DTO 应该只负责传输特定类型的数据,避免包含过多不相关的属性。
- 简洁性:尽量保持 DTO 类的简洁,只包含必要的属性。避免添加不必要的方法或逻辑。
- 可维护性:使用有意义的类名和属性名,便于代码的理解和维护。
处理复杂对象结构
当需要传输复杂对象结构时,可以使用嵌套的 DTO。例如,如果一个订单包含多个订单项,可以创建一个 OrderItemDTO
并在 OrderDTO
中包含一个 OrderItemDTO
的列表。
public class OrderItemDTO {
private long itemId;
private String productName;
private int quantity;
private double price;
// getters and setters
public long getItemId() {
return itemId;
}
public void setItemId(long itemId) {
this.itemId = itemId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
import java.util.List;
public class OrderDTO {
private long orderId;
private List<OrderItemDTO> orderItems;
private double totalPrice;
// getters and setters
public long getOrderId() {
return orderId;
}
public void setOrderId(long orderId) {
this.orderId = orderId;
}
public List<OrderItemDTO> getOrderItems() {
return orderItems;
}
public void setOrderItems(List<OrderItemDTO> orderItems) {
this.orderItems = orderItems;
}
public double getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(double totalPrice) {
this.totalPrice = totalPrice;
}
}
数据验证
在使用 DTO 时,应该对输入数据进行验证。可以使用 Java Bean Validation API 来对 DTO 的属性进行验证。
例如,我们对 CreateUserDTO
进行验证:
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Positive;
public class CreateUserDTO {
@NotBlank(message = "Name cannot be blank")
private String name;
@Positive(message = "Age must be a positive number")
private int age;
@Email(message = "Invalid email format")
private String email;
// getters and setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
在 Spring Controller 中,可以通过 @Valid
注解来触发验证:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@Validated
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/users")
public UserDTO createUser(@Valid @RequestBody CreateUserDTO createUserDTO) {
// 处理创建用户逻辑
}
}
小结
数据传输对象(DTO)在 Java 开发中是一种非常重要的设计模式,它通过封装数据在不同层之间进行传递,有效地降低了各层之间的耦合度,提高了代码的可维护性和可扩展性。通过遵循一些设计原则和最佳实践,如单一职责原则、简洁性、处理复杂对象结构和数据验证等,可以更好地使用 DTO 来构建健壮、高效的应用程序。
参考资料
- 《Effective Java》 - Joshua Bloch
- Oracle Java Documentation
- Spring Framework Documentation