0

[Java Backend Zero to Hello] [Phase 6] BÀI 6.3: DTO & MAPPER

📚 Series: Java Backend Zero to Hello 📂 Phân đoạn: Phase 6: REST API & Best Practices 📖 Nội dung: BÀI 6.3: DTO & MAPPER 💡 Khóa học lập trình Backend Java & Spring Boot chuẩn doanh nghiệp từ con số 0.


BÀI 6.3: DTO & MAPPER

Mục tiêu

  • Hiểu DTO pattern
  • Tách biệt Entity và DTO
  • Sử dụng MapStruct
  • Áp dụng vào project thực tế

1. TẠI SAO CẦN DTO?

Vấn đề khi trả Entity trực tiếp

// ❌ Trả entity trực tiếp
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
    return userRepository.findById(id).orElseThrow();
}

Vấn đề:

  • Lộ thông tin nhạy cảm (password, internal fields)
  • Khó thay đổi API mà không ảnh hưởng DB
  • Lazy loading exception
  • Circular reference

Giải pháp - DTO

// ✅ Trả DTO
@GetMapping("/{id}")
public UserResponse getById(@PathVariable Long id) {
    User user = userRepository.findById(id).orElseThrow();
    return UserResponse.from(user);
}

2. CÁC LOẠI DTO

2.1 Request DTO

public record CreateUserRequest(
    @NotBlank String name,
    @Email String email,
    @Min(0) Integer age
) {}

2.2 Response DTO

public record UserResponse(
    Long id,
    String name,
    String email,
    Integer age,
    LocalDateTime createdAt
) {
    public static UserResponse from(User user) {
        return new UserResponse(
            user.getId(),
            user.getName(),
            user.getEmail(),
            user.getAge(),
            user.getCreatedAt()
        );
    }
}

2.3 Update DTO

public record UpdateUserRequest(
    @Size(min = 2, max = 100) String name,
    @Email String email,
    @Min(0) @Max(150) Integer age
) {}

2.4 Summary DTO (cho list)

public record UserSummary(
    Long id,
    String name,
    String email
) {}

3. MAP THỦ CÔNG

3.1 Static factory method

public record UserResponse(...) {
    public static UserResponse from(User user) {
        return new UserResponse(...);
    }

    public static List<UserResponse> fromList(List<User> users) {
        return users.stream().map(UserResponse::from).toList();
    }
}

3.2 Service method

@Service
public class UserService {

    public UserResponse toResponse(User user) {
        return new UserResponse(...);
    }
}

4. MAPSTRUCT

MapStruct tự động generate code map giữa các object.

4.1 Dependency

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.5.Final</version>
</dependency>

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.5.Final</version>
    <scope>provided</scope>
</dependency>

4.2 Mapper interface

@Mapper(componentModel = "spring")
public interface UserMapper {

    UserResponse toResponse(User user);

    List<UserResponse> toResponseList(List<User> users);

    @Mapping(target = "id", ignore = true)
    @Mapping(target = "createdAt", ignore = true)
    User toEntity(CreateUserRequest request);

    @Mapping(target = "id", ignore = true)
    @Mapping(target = "createdAt", ignore = true)
    void updateEntity(UpdateUserRequest request, @MappingTarget User user);
}

4.3 Sử dụng

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    private final UserMapper userMapper;

    public UserResponse getById(Long id) {
        User user = userRepository.findById(id).orElseThrow();
        return userMapper.toResponse(user);
    }

    public UserResponse create(CreateUserRequest request) {
        User user = userMapper.toEntity(request);
        User saved = userRepository.save(user);
        return userMapper.toResponse(saved);
    }
}

4.4 Custom mapping

@Mapper(componentModel = "spring")
public interface UserMapper {

    @Mapping(target = "fullName", source = "name")
    @Mapping(target = "emailAddress", source = "email")
    UserResponse toResponse(User user);

    @Mapping(target = "createdAt",
             expression = "java(java.time.LocalDateTime.now())")
    User toEntity(CreateUserRequest request);
}

5. MODELMAPPER

Thư viện mapping phổ biến khác.

5.1 Dependency

<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>3.1.1</version>
</dependency>

5.2 Cấu hình

@Configuration
public class ModelMapperConfig {

    @Bean
    public ModelMapper modelMapper() {
        ModelMapper mapper = new ModelMapper();
        mapper.getConfiguration()
            .setMatchingStrategy(MatchingStrategies.STRICT)
            .setFieldMatchingEnabled(true)
            .setSkipNullEnabled(true);
        return mapper;
    }
}

5.3 Sử dụng

@Service
@RequiredArgsConstructor
public class UserService {

    private final ModelMapper modelMapper;

    public UserResponse toResponse(User user) {
        return modelMapper.map(user, UserResponse.class);
    }
}

6. SO SÁNH

MapStruct ModelMapper
Compile-time Runtime
Type-safe Reflection
Nhanh hơn Chậm hơn
Code rõ ràng Ít code
Cần khai báo Tự động

7. BEST PRACTICES

7.1 ✅ NÊN

// Tách biệt Entity và DTO
public class User { ... }  // Entity
public record UserResponse(...) { ... }  // DTO

// Dùng MapStruct cho project lớn
@Mapper(componentModel = "spring")
public interface UserMapper { ... }

// Dùng record cho DTO (immutable)
public record UserResponse(Long id, String name) {}

7.2 ❌ TRÁNH

// Trả entity trực tiếp
return userRepository.findById(id);  // ❌

// Dùng entity làm DTO
public class UserDTO extends User { ... }  // ❌

// Map thủ công nhiều field
return new UserResponse(
    user.getId(),
    user.getName(),
    user.getEmail(),
    user.getAge(),
    user.getCreatedAt(),
    user.getUpdatedAt(),
    // ... 20 fields
);

8. VÍ DỤ HOÀN CHỈNH

8.1 Entity

@Entity
@Table(name = "users")
public class User {
    @Id @GeneratedValue
    private Long id;
    private String name;
    private String email;
    private String password;  // Không lộ ra ngoài
    private Integer age;
    private LocalDateTime createdAt;
    // getters, setters
}

8.2 DTOs

public record CreateUserRequest(
    @NotBlank String name,
    @Email String email,
    @NotBlank @Size(min = 8) String password,
    @Min(0) Integer age
) {}

public record UpdateUserRequest(
    String name,
    String email,
    @Min(0) Integer age
) {}

public record UserResponse(
    Long id,
    String name,
    String email,
    Integer age,
    LocalDateTime createdAt
) {}

public record UserSummary(
    Long id,
    String name
) {}

8.3 Mapper

@Mapper(componentModel = "spring")
public interface UserMapper {

    UserResponse toResponse(User user);

    List<UserResponse> toResponseList(List<User> users);

    UserSummary toSummary(User user);

    @Mapping(target = "id", ignore = true)
    @Mapping(target = "password", ignore = true)
    @Mapping(target = "createdAt", ignore = true)
    User toEntity(CreateUserRequest request);

    @Mapping(target = "id", ignore = true)
    @Mapping(target = "password", ignore = true)
    @Mapping(target = "createdAt", ignore = true)
    void updateEntity(UpdateUserRequest request, @MappingTarget User user);
}

8.4 Service

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    private final UserMapper userMapper;
    private final PasswordEncoder passwordEncoder;

    public UserResponse create(CreateUserRequest request) {
        User user = userMapper.toEntity(request);
        user.setPassword(passwordEncoder.encode(request.password()));
        User saved = userRepository.save(user);
        return userMapper.toResponse(saved);
    }

    public UserResponse update(Long id, UpdateUserRequest request) {
        User user = userRepository.findById(id).orElseThrow();
        userMapper.updateEntity(request, user);
        User saved = userRepository.save(user);
        return userMapper.toResponse(saved);
    }

    public UserResponse getById(Long id) {
        return userMapper.toResponse(
            userRepository.findById(id).orElseThrow()
        );
    }

    public List<UserSummary> getAll() {
        return userRepository.findAll().stream()
            .map(userMapper::toSummary)
            .toList();
    }
}

9. BÀI TẬP THỰC HÀNH

Bài 1: User DTO

Tạo đầy đủ DTO cho User: Create, Update, Response, Summary.

Bài 2: MapStruct

Cấu hình MapStruct và tạo mapper cho User.

Bài 3: Service Integration

Tích hợp mapper vào UserService.


10. TÓM TẮT

Khái niệm Mô tả
DTO Data Transfer Object
Entity Ánh xạ database
MapStruct Code generation
ModelMapper Reflection-based
@Mapping Custom mapping
@MappingTarget Update existing

Bài tiếp theo: 6.4 SOLID Principles


🧭 Điều hướng bài học

⭐ Hãy bookmark (clip) lại series để tiện theo dõi các bài học tiếp theo nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí