Uso de DTOs y mapeo de objetos
En aplicaciones profesionales no es buena práctica devolver directamente las entidades de la base de datos.
En su lugar se usan DTOs.
DTO significa:
Data Transfer Object
Son objetos diseñados específicamente para enviar o recibir datos en la API.
1. Problema de devolver entidades directamente #
Supongamos esta entidad:
1@Entity 2public class User { 3 4 @Id 5 @GeneratedValue 6 private Long id; 7 8 private String name; 9 10 private String email; 11 12 private String password; 13 14}
Si devolvemos esto directamente:
1@GetMapping("/users") 2public List<User> getUsers() { 3 return repository.findAll(); 4}
La respuesta sería:
1[ 2 { 3 "id": 1, 4 "name": "Ana", 5 "email": "ana@email.com", 6 "password": "123456" 7 } 8]
⚠️ Problema: estamos enviando la contraseña.
Esto es muy peligroso.
2. Qué es un DTO #
Un DTO es una clase que contiene solo los datos que queremos exponer.
3. Ejemplo #
1public class UserDTO { 2 3 private Long id; 4 5 private String name; 6 7 private String email; 8 9}
Ahora no existe el campo password.
4. Usar DTO en el Controller #
1@GetMapping("/users") 2public List<UserDTO> getUsers() { 3 4 List<User> users = repository.findAll(); 5 6 return users.stream() 7 .map(user -> new UserDTO( 8 user.getId(), 9 user.getName(), 10 user.getEmail() 11 )) 12 .toList(); 13}
5. Resultado #
1[ 2 { 3 "id": 1, 4 "name": "Ana", 5 "email": "ana@email.com" 6 } 7]
La contraseña ya no aparece.
6. DTO para crear usuarios #
También podemos usar DTOs para recibir datos.
7. Ejemplo #
1public class CreateUserDTO { 2 3 private String name; 4 5 private String email; 6 7 private String password; 8 9}
8. Controller #
1@PostMapping("/users") 2public User createUser(@RequestBody CreateUserDTO dto) { 3 4 User user = new User(); 5 6 user.setName(dto.getName()); 7 user.setEmail(dto.getEmail()); 8 user.setPassword(dto.getPassword()); 9 10 return repository.save(user); 11 12}
9. Tipos de DTO comunes #
En proyectos grandes suele haber varios DTO.
10. Ejemplo #
1UserDTO 2CreateUserDTO 3UpdateUserDTO 4UserResponseDTO
Ejemplo #
1public class UpdateUserDTO { 2 3 private String name; 4 5}
11. Problema del mapeo manual #
Si tenemos muchas entidades, convertir manualmente puede ser pesado.
Ejemplo:
1UserDTO dto = new UserDTO( 2 user.getId(), 3 user.getName(), 4 user.getEmail() 5);
Si hay muchas entidades esto se vuelve repetitivo.
12. Usar MapStruct #
Para solucionar esto se usa MapStruct.
MapStruct genera automáticamente el código de conversión.
13. Añadir dependencia MapStruct #
En pom.xml:
1<dependency> 2 <groupId>org.mapstruct</groupId> 3 <artifactId>mapstruct</artifactId> 4 <version>1.5.5.Final</version> 5</dependency>
14. Crear un mapper #
1@Mapper(componentModel = "spring") 2public interface UserMapper { 3 4 UserDTO toDTO(User user); 5 6 User toEntity(CreateUserDTO dto); 7 8}
Spring generará automáticamente el código.
15. Usar el mapper #
1@Service 2public class UserService { 3 4 private final UserRepository repository; 5 private final UserMapper mapper; 6 7 public UserService(UserRepository repository, UserMapper mapper) { 8 this.repository = repository; 9 this.mapper = mapper; 10 } 11 12 public List<UserDTO> getUsers() { 13 14 return repository.findAll() 15 .stream() 16 .map(mapper::toDTO) 17 .toList(); 18 19 } 20 21}
16. Ejemplo completo arquitectura #
1Controller 2 ↓ 3Service 4 ↓ 5Repository 6 ↓ 7Entity
DTOs se usan entre:
1Controller ↔ Service
17. Flujo completo #
Cliente envía JSON:
1{ 2 "name": "Ana", 3 "email": "ana@email.com", 4 "password": "123456" 5}
Flujo:
1JSON → CreateUserDTO → Entity → Database
Respuesta:
1Entity → UserDTO → JSON
18. Buenas prácticas con DTO #
No devolver entidades #
Siempre usar DTO.
Separar DTO de entrada y salida #
Ejemplo:
1CreateUserDTO 2UserResponseDTO
Validar DTO #
Ejemplo:
1public class CreateUserDTO { 2 3 @NotBlank 4 private String name; 5 6 @Email 7 private String email; 8 9}
Usar mappers #
Evita escribir conversiones manualmente.
19. Ventajas de usar DTO #
| ventaja | explicación |
|---|---|
| seguridad | no expones campos sensibles |
| flexibilidad | API independiente de DB |
| control | decides qué enviar |
| mejor diseño | arquitectura limpia |
Resumen del tema
Conceptos clave #
- Propósito de los DTOs (Data Transfer Objects): evitar la sobreexposición de datos sensibles (contraseñas, tokens), eliminar problemas de recursión infinita en relaciones bidireccionales y desacoplar la base de datos de la API pública.
- Especialización de DTOs: diferenciación clara entre modelos de entrada (
CreateUserDTO,UpdateUserDTOcon anotaciones de validación) y modelos de salida (UserResponseDTO). - Mapeo Automático con MapStruct: generación de código en tiempo de compilación mediante interfaces anotadas con
@Mapper(componentModel = "spring"), evitando la sobrecarga de reflexión y el código manual repetitivo. - Java Records: uso de
recordpara DTOs inmutables, concisos y libres de boilerplate.
Qué debes recordar #
Usa DTOs separados para peticiones y respuestas, delegando la conversión entidad-DTO en MapStruct para garantizar seguridad, inmutabilidad y rendimiento.