杰瑞科技汇

Entity和DTO如何转换?

Of course! This is a fundamental concept in Java development, especially in multi-layered applications. Let's break down what Entities and DTOs are, why we need them, and how to use them effectively.

Entity和DTO如何转换?-图1
(图片来源网络,侵删)

The Core Problem: Why Not Use the Same Object?

Imagine you have a simple User object in your database. It might look like this:

// A class that directly maps to a database table
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password; // Sensitive!
    private String email;
    private String role; // e.g., "USER", "ADMIN"
    private LocalDateTime createdAt;
    // ... getters and setters
}

Now, consider the following scenarios:

  1. API Security: You want to send user data to a client (like a web browser or mobile app). Should you send the password and role fields? Absolutely not! The client doesn't need the password, and exposing the role is a security risk.
  2. API Stability: Your database User table has 15 fields. But your registration API only needs 3: username, email, and password. You don't want to create a 15-field object on the client side just to send 3 fields.
  3. Data Transformation: Your database uses snake_case (first_name), but your API uses camelCase (firstName). You need a way to convert between these formats.
  4. Circular References: Your User might have a list of Posts, and each Post might have a reference back to the User. If you try to serialize this directly (e.g., to JSON), you'll get an infinite loop and a stack overflow error.

The solution is to use different objects for different contexts. This is where Entities and DTOs come in.


What is an Entity?

An Entity is a Java class that represents a table in your database. It's the core of your persistence layer.

Entity和DTO如何转换?-图2
(图片来源网络,侵删)
  • Purpose: To map objects to database records using a technology like JPA (Java Persistence API) and Hibernate.
  • Scope: Lives within your application's backend, typically in your domain or entity layer.
  • Key Characteristics:
    • Annotated with @Entity: Marks it as a JPA entity.
    • Has an @Id: Has a primary key, usually annotated with @Id and @GeneratedValue.
    • Database Mapping: Fields are mapped to database columns (often with @Column).
    • Contains Business Logic: Can have business methods (e.g., isAdmin()).
    • Knows about Persistence: It's aware of the database.
    • May contain sensitive data: Like passwords, internal status codes, etc.

Example Entity:

import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "users") // Maps to the "users" table
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false, unique = true)
    private String username;
    @Column(nullable = false)
    private String password; // Sensitive data!
    @Column(nullable = false, unique = true)
    private String email;
    @Enumerated(EnumType.STRING)
    private Role role; // e.g., Role.USER, Role.ADMIN
    @Column(name = "created_at", nullable = false)
    private LocalDateTime createdAt;
    // Constructors, Getters, and Setters
    // ...
}
enum Role {
    USER, ADMIN
}

What is a DTO (Data Transfer Object)?

A DTO is a simple, plain Java object (POJO) whose primary purpose is to transfer data between different layers of your application or between your application and an external client.

  • Purpose: To define a clean, secure, and tailored data contract for a specific use case.
  • Scope: Used in the service layer, controller layer, and for API communication.
  • Key Characteristics:
    • No Annotations: It has no @Entity, @Id, or JPA annotations. It's persistence-ignorant.
    • Flat Structure: Designed for a specific purpose. It might contain only a subset of the entity's fields.
    • No Business Logic: Typically just fields, constructors, and getters/setters. It's a data carrier.
    • Security: Allows you to expose only the data you want to expose. You can create a UserCreationDto that doesn't include the id or role.
    • No ORM Mapping: It's not tied to any database table.

Example DTOs:

// DTO for creating a new user (no id, no role)
public record UserCreationDto(
    String username,
    String password,
    String email
) {}
// DTO for sending user data to a client (no password, includes role)
public record UserSummaryDto(
    Long id,
    String username,
    String email,
    Role role
) {}
// DTO for updating a user (only fields that can be updated)
public record UserUpdateDto(
    String email
) {}

(Using record is modern and concise for simple DTOs, but a traditional class with getters/setters works just as well).


The Bridge: Converting Between Entities and DTOs

You need a way to convert from an Entity to a DTO and vice versa. There are three common approaches:

a) Manual Mapping (Simple, but error-prone)

public class UserMapper {
    public UserSummaryDto toSummaryDto(User user) {
        if (user == null) {
            return null;
        }
        return new UserSummaryDto(
            user.getId(),
            user.getUsername(),
            user.getEmail(),
            user.getRole()
        );
    }
    public User toEntity(UserCreationDto dto) {
        if (dto == null) {
            return null;
        }
        User user = new User();
        user.setUsername(dto.username());
        user.setPassword(dto.password()); // In a real app, you'd hash this!
        user.setEmail(dto.email());
        return user;
    }
}

b) Using a Mapping Library (Recommended)

Libraries like MapStruct or ModelMapper automate this process. They generate the mapping code at compile time, which is type-safe and highly performant.

Example with MapStruct:

  1. Define a Mapper Interface:

    import org.mapstruct.Mapper;
    import org.mapstruct.Mapping;
    import org.mapstruct.factory.Mappers;
    @Mapper // MapStruct will generate an implementation of this interface
    public interface UserMapper {
        UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
        // MapStruct will automatically map fields with the same name.
        // For fields with different names, use @Mapping.
        @Mapping(target = "createdAt", ignore = true) // Don't map this field
        UserSummaryDto userToUserSummaryDto(User user);
        @Mapping(target = "id", ignore = true) // Don't map the ID on creation
        @Mapping(target = "createdAt", expression = "java(java.time.LocalDateTime.now())") // Set a value
        User userCreationDtoToUser(UserCreationDto dto);
    }
  2. Use the Mapper in your Service:

    @Service
    public class UserService {
        @Autowired
        private UserRepository userRepository;
        public UserSummaryDto createUser(UserCreationDto creationDto) {
            User user = UserMapper.INSTANCE.userCreationDtoToUser(creationDto);
            User savedUser = userRepository.save(user);
            return UserMapper.INSTANCE.userToUserSummaryDto(savedUser);
        }
    }

Putting It All Together: A Typical Flow

Here's how these pieces work in a typical web application:

  1. Client Request: A client sends a JSON payload to create a user.

    {
      "username": "johndoe",
      "password": "securePassword123",
      "email": "john.doe@example.com"
    }
  2. Controller Layer: The @RestController receives this JSON and Spring deserializes it into a UserCreationDto.

    @RestController
    @RequestMapping("/api/users")
    public class UserController {
        @Autowired
        private UserService userService;
        @PostMapping
        public ResponseEntity<UserSummaryDto> createUser(@RequestBody UserCreationDto creationDto) {
            UserSummaryDto createdUser = userService.createUser(creationDto);
            return new ResponseEntity<>(createdUser, HttpStatus.CREATED);
        }
    }
  3. Service Layer: The UserService takes the UserCreationDto. It uses a mapper (like MapStruct) to convert the DTO into a User Entity.

    @Service
    public class UserService {
        public UserSummaryDto createUser(UserCreationDto creationDto) {
            User userEntity = UserMapper.INSTANCE.userCreationDtoToUser(creationDto);
            // ... hash password, etc.
            User savedEntity = userRepository.save(userEntity);
            return UserMapper.INSTANCE.userToUserSummaryDto(savedEntity);
        }
    }
  4. Repository Layer: The UserRepository (a Spring Data JPA interface) saves the User entity to the database.

  5. Response: The service converts the saved User entity back into a UserSummaryDto (which excludes the password) and returns it to the controller. The controller then sends this DTO back to the client as a JSON response.

{
  "id": 1,
  "username": "johndoe",
  "email": "john.doe@example.com",
  "role": "USER"
}

Summary Table: Entity vs. DTO

Feature Entity DTO (Data Transfer Object)
Primary Purpose Map to a database table. Transfer data between layers or to a client.
Persistence Yes, it's a persistent object. No, it's persistence-ignorant.
Annotations @Entity, @Id, @Column, etc. None (or custom framework-specific ones).
Data Shape Represents the full database table structure. Tailored for a specific use case (e.g., create, update, view).
Sensitive Data May contain sensitive data (passwords, roles). Excludes sensitive data by design.
Business Logic Can contain business logic methods. Typically a simple data carrier (no logic).
Location Backend (domain/persistence layer). Service, controller, and API boundary layers.
Circular Refs Can have them (e.g., User -> List -> User). Designed to avoid them (e.g., UserSummaryDto with a list of PostSummaryDto).
分享:
扫描分享到社交APP
上一篇
下一篇