DTO

(DTO — Data Transfer Object)

Un DTO (Data Transfer Object) es una clase ligera usada para transportar datos entre capas (por ejemplo, entre la capa de persistencia y la capa de presentación). Su objetivo es:

  • Evitar exponer entidades JPA directamente en la API.
  • Reducir la cantidad de datos transferidos (seleccionar sólo lo necesario).
  • Desacoplar la representación externa (API) del modelo de persistencia.

Cuándo usar DTOs:

  • En respuestas de API públicas (REST) para no devolver entidades gestionadas.
  • Cuando la vista necesita una combinación de campos de varias entidades.
  • Para mejorar seguridad y control sobre los campos expuestos.

Dependencia (Maven)

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

Ejemplo simple

1) Entidad User

@Entity
public class User {
    @Id @GeneratedValue
    private Long id;
    private String name;
    private String email;
    // getters/setters
}

2) DTO UserDto

public class UserDto {
    private Long id;
    private String name;
    // no incluimos email por ejemplo
    // getters/setters
}

3) Bean de configuración de ModelMapper (Spring)

@Configuration
public class ModelMapperConfig {
    @Bean
    public ModelMapper modelMapper() {
        ModelMapper mapper = new ModelMapper();
        // configuración opcional, ej. ignorar nulls, políticas de matching, etc.
        return mapper;
    }
}

4) Uso en un Service para mapear entidad -> DTO

@Service
public class UserService {
    private final UserRepository repo;
    private final ModelMapper mapper;

    public UserService(UserRepository repo, ModelMapper mapper) {
        this.repo = repo;
        this.mapper = mapper;
    }

    public UserDto findById(Long id) {
        User user = repo.findById(id).orElseThrow();
        return mapper.map(user, UserDto.class);
    }

    public List<UserDto> findAll() {
        return repo.findAll()
            .stream()
            .map(u -> mapper.map(u, UserDto.class))
            .collect(Collectors.toList());
    }
}

Mapeo de colecciones

ModelMapper puede mapear listas con streams (como en el ejemplo). También se puede usar TypeToken para mapear listas directamente:

Type listType = new TypeToken<List<UserDto>>() {}.getType();
List<UserDto> dtos = mapper.map(userList, listType);

Mapeos personalizados (campos anidados o nombres distintos)

Si el DTO y la entidad no tienen nombres compatibles, se puede usar PropertyMap:

PropertyMap<User, UserDto> userMap = new PropertyMap<>() {
    @Override
    protected void configure() {
        map().setName(source.getName());
        // map nested: map().setCity(source.getAddress().getCity());
    }
};
mapper.addMappings(userMap);

O usar TypeMap para reglas más sofisticadas:

TypeMap<User, UserDto> typeMap = mapper.createTypeMap(User.class, UserDto.class);
typeMap.addMappings(m -> m.map(src -> src.getName(), UserDto::setName));

Mapear sobre un objeto existente (actualizar DTO -> entidad o viceversa)

// actualizar entidad existente con datos del DTO
mapper.map(userDto, existingUser);

Ignorar nulos

Para no sobrescribir campos con null al mapear sobre un objeto existente:

mapper.getConfiguration().setPropertyCondition(Conditions.isNotNull());

Notas prácticas y buenas prácticas

  • Mantener los DTOs lo más simples posible: únicamente campos que la vista necesite.
  • Usar mapeos personalizados para casos donde la estructura difiere (campos anidados, renombrados, formatos).
  • Para mapeos complejos o mucho tráfico, considerar escribir conversiones manuales (mayor control y rendimiento).
  • No exponer entidades JPA en APIs públicas; usar DTOs para evitar lazy-loading y problemas de serialización.

Si quieres, añado ejemplos concretos adaptados a las entidades que ya tienes en el proyecto (por ejemplo Post/Comment o Student/Course).