0

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

Java Backend Zero to Hello

📚 Bài viết thuộc series Java Backend Zero to Hello 📌 Phần: Phase 6: REST API & Best Practices | Bài 62/86


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 Series

⬅️ Bài trước: BÀI 6.2: API DOCUMENTATION (OPENAPI/SWAGGER)

📋 Lộ trình tổng quan: Xem Toàn Bộ Series

➡️ Bài tiếp theo: BÀI 6.4: SOLID PRINCIPLES


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í