跳转至

Java 中的数据传输对象(Data Transfer Object)

简介

在 Java 开发中,数据传输对象(Data Transfer Object,简称 DTO)是一种设计模式,它用于在不同的层(如表示层、业务逻辑层和数据访问层)之间传输数据。其主要目的是减少不同层之间的耦合,提高代码的可维护性和可扩展性。通过使用 DTO,可以将相关的数据封装在一起,以一种统一、简洁的方式在各层之间传递,避免了在方法参数中传递大量分散的数据。

目录

  1. 基础概念
  2. 使用方法
    • 创建 DTO 类
    • 在不同层之间传递 DTO
  3. 常见实践
    • 与数据库交互时使用 DTO
    • 与 RESTful API 结合使用 DTO
  4. 最佳实践
    • DTO 的设计原则
    • 处理复杂对象结构
    • 数据验证
  5. 小结
  6. 参考资料

基础概念

数据传输对象本质上是一个简单的 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 的设计原则

  1. 单一职责原则:每个 DTO 应该只负责传输特定类型的数据,避免包含过多不相关的属性。
  2. 简洁性:尽量保持 DTO 类的简洁,只包含必要的属性。避免添加不必要的方法或逻辑。
  3. 可维护性:使用有意义的类名和属性名,便于代码的理解和维护。

处理复杂对象结构

当需要传输复杂对象结构时,可以使用嵌套的 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 来构建健壮、高效的应用程序。

参考资料

  1. 《Effective Java》 - Joshua Bloch
  2. Oracle Java Documentation
  3. Spring Framework Documentation