ANDRES ALFONSO MIRA MEJIA
✅
Actualizar registros es tan común como crearlos, y en una API REST con Spring Boot necesitas un endpoint PUT bien estructurado para hacerlo de forma segura. Aquí aprendes a construir un endpoint PUT que valida la existencia del recurso, controla qué campos se modifican y persiste los cambios usando MapStruct y JPA.
¿Por qué usar un DTO específico para actualizar?
No siempre quieres permitir que se editen todos los campos de un recurso. En el caso de una película, cambiar el género o la duración no tiene sentido, pero sí actualizar el título, la fecha de estreno y el rating.
Por eso conviene crear un UpdateMovieDTO separado del MovieDTO original. Este nuevo objeto solo expone los tres campos editables y deja fuera la clave primaria y los datos que deben permanecer inmutables.
¿Qué es un DTO de actualización? Es un objeto de transferencia de datos que contiene únicamente los campos que el cliente puede modificar. Protege tu modelo de cambios no autorizados y hace explícita la intención del endpoint.
¿Cómo se define el DTO en el código?
Duplicas el MovieDTO existente, lo renombras a UpdateMovieDTO y eliminas los atributos que no quieres exponer: el id, la duración y el género. El resultado es un DTO mínimo con solo tres propiedades [02:00].
¿Cómo construir el endpoint PUT en el controlador?
El controlador recibe dos piezas de información: el id del recurso por la URL y el cuerpo con los nuevos valores. Para eso usas la anotación @PutMapping combinada con @PathVariable y @RequestBody.
La firma queda así: el id viaja como variable de ruta y el UpdateMovieDTO llega en el cuerpo de la petición. El controlador delega la lógica al servicio y devuelve un ResponseEntity.ok con el DTO actualizado.
java @PutMapping("/{id}") public ResponseEntity<MovieDTO> update( @PathVariable Long id, @RequestBody UpdateMovieDTO updateMovieDTO) { return ResponseEntity.ok(movieService.update(id, updateMovieDTO)); }
¿Cómo validar la existencia antes de actualizar?
Antes de modificar cualquier dato, debes confirmar que el recurso existe. La estrategia es buscar el MovieEntity en la base con findById y revisar el resultado [04:30].
Si el entity es null, retornas null porque no hay nada que actualizar. Si existe, aplicas los cambios sobre ese objeto recuperado y luego lo guardas. Este patrón evita crear registros fantasma y garantiza que solo modifiques lo que ya está persistido.
¿Qué pasa si el id no existe en la base de datos? El método retorna null y el cliente puede interpretarlo como recurso no encontrado. Así proteges la integridad de tu base.
¿Cómo usar MapStruct con MappingTarget para actualizar entidades?
Asignar campo por campo con setTítulo, setFechaEstreno y setClasificación funciona, pero ensucia el código. MapStruct ofrece una solución más limpia con la anotación @MappingTarget.
Esta anotación le dice a MapStruct que reciba el objeto por referencia y aplique los mapeos sobre esa misma instancia, sin crear uno nuevo. El método no retorna nada porque modifica el entity in place.
¿Cómo se declara el método en el mapper?
Dentro del MovieMapper defines un método updateEntityFromDTO que recibe el UpdateMovieDTO y el MovieEntity anotado con @MappingTarget. Luego declaras tres @Mapping para enlazar los campos:
java void updateEntityFromDTO( UpdateMovieDTO updateMovieDTO, @MappingTarget MovieEntity movieEntity);
¿Qué ventaja tiene el paso por referencia?
Al pasar el MovieEntity por referencia, los cambios se reflejan directamente en el objeto recuperado de la base. No necesitas asignar el resultado a una variable nueva ni hacer conversiones manuales. Una sola línea reemplaza tres setters.
¿Cómo se conecta el repositorio con el servicio?
En el MovieRepository defines el método update que recibe el id y el UpdateMovieDTO y retorna un MovieDTO. La implementación en MovieEntityRepository hace tres cosas en orden: busca por id, aplica el mapper con @MappingTarget y guarda con crudRepository.save [07:15].
Un detalle importante: el campo rating llega como double desde el DTO, pero el entity lo maneja como BigDecimal. Resuelves esa incompatibilidad con BigDecimal.valueOf al asignar el valor.
El servicio expone un método update que simplemente delega al repositorio pasando ambos parámetros. Esa separación de capas mantiene cada clase con una responsabilidad clara.
¿Cómo probar el endpoint en Postman?
Con el servicio reiniciado, lanzas una petición PUT a localhost:8090/platzi-play/movies/91, donde 91 es el id de la película a editar. En el body envías solo los tres campos editables: título, releaseDate y rating.
La respuesta llega con código 200 y al hacer un GET sobre el mismo id verificas que la fecha cambió de 2010-06-25 a 2010-06-29 y el rating subió de 3.5 a 4.5 [10:20]. Los demás campos permanecen intactos, justo como debe comportarse una actualización controlada.
Ahora tu API soporta crear y actualizar recursos como cualquier aplicación moderna. ¿Te animas a implementar el reto del @DeleteMapping para eliminar una película por su id? Déjame tu solución en los comentarios.
Primero agregue el metodo para borrar en MovieRepository:
package com.platzi.play.domain.repository; import com.platzi.play.domain.dto.MovieDto; import com.platzi.play.domain.dto.UpdateMovieDto; import java.util.List; public interface MovieRepository { List<MovieDto> getAll(); MovieDto getById(Long id); MovieDto save(MovieDto movieDto); MovieDto update(long id, UpdateMovieDto updateMovieDto); MovieDto delete(long id); } ```Luego agregue su funcionamiento en la clase MovieEntityRepository: ```java @Override public MovieDto delete(long id) { MovieEntity movieEntity = this.crudMovieEntity.findById(id).orElse(null); if (movieEntity == null) return null; this.crudMovieEntity.delete(movieEntity); return this.movieMapper.toDto(movieEntity); } ```Luego en MovieService cree el metodo que se usara en el controller posteriormente: ```java public MovieDto delete(long id){ return this.movieRepository.delete(id); } ```Finalmente implemente este metodo del service en el MovieController: ```java @DeleteMapping("/{id}") public ResponseEntity<MovieDto> delete(@PathVariable long id){ return ResponseEntity.ok(this.movieService.delete(id)); }
Mi solución fue la siguiente:
Cree un método en la interfaz MovieRepository
Hice la implementación usan Streams
Implemente el método delete en la clase MovieService
Implemente el método en el Controlador llamando al método del servicio
ANDRES ALFONSO MIRA MEJIA
✅
Xavier Armando Larios Alvarenga
JHONATAN AGUILERA GARCIA
Frederick Lozano
Hola todos,
No entiendo por que hacer 2 select a la base de datos ?
creo que solo deberia haber uno, validar si retorna null
algo asi:
@PutMapping("/{id}")public ResponseEntity<MovieDto> update(@PathVariable long id, @RequestBody UpdateMovieDto updateMovieDto) { MovieDto movieDto = this.movieService.update(id, updateMovieDto); if (movieDto == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(movieDto);}
PD: "puede que lo que diga esta mal"
Ingenieria Resuelve
porque en el metodo update primero va el target y luego el source??
Milton Hernan Camacho Quitian
Comparto mi solución, pero hice un borrado lógico, para no perder información de la entidad:
//Solicitud HTTP en Postman { "status": "D" // o "I" para INACTIVE } //Creación de Record para manejar el estado package com.platzi.play.domain.dto; public record LogicDeleteDTO(String status) {} //MovieRepository MovieDTO deleteLogic(long id, LogicDeleteDTO logicDeleteDTO); //MovieMapper @Mapping(target = "estado", source = "status") void updateEntityStatus(LogicDeleteDTO logicDeleteDTO, @MappingTarget MovieEntity movieEntity); //MovieEntityRepository public MovieDTO deleteLogic(long id, LogicDeleteDTO logicDeleteDTO) { MovieEntity movieEntity = crudMovieEntity.findById(id).orElse(null); if (movieEntity == null) return null; this.movieMapper.updateEntityStatus(logicDeleteDTO, movieEntity); return this.movieMapper.toDto(this.crudMovieEntity.save(movieEntity));} //MovieController @PutMapping("change-status/{id}") public ResponseEntity<MovieDTO> deleteMovieLogic(@PathVariable long id, @RequestBody LogicDeleteDTO logicDeleteDTO) { return ResponseEntity.ok(this.movieService.deleteLogic(id, logicDeleteDTO));}
Me gustaría saber si estoy utilizando la lógica adecuada o incluso el StatusMapper de forma correcta.
Solución al reto Controller
@DeleteMapping("/{id}") public ResponseEntity<String> deleteMovie(@PathVariable long id) { this.movieService.deleteMovie(id); return ResponseEntity.ok("Movie has been deleted"); }
Repository
void deleteMovie(long id);
Service
public void deleteMovie(long id) { this.movieRepository.deleteMovie(id); }
MovieEntityRepository
@Overridepublic void deleteMovie(long id) { if(this.crudMovieEntity.findById(id).isPresent()) { this.crudMovieEntity.deleteById(id); }}
Comparto mi solución para eliminar una película.
En la interfaz MovieRepository se agregó el método delete:
void delete(long id);
Luego, en la clase MovieEntityRepository se realizó la implementación:
@Override public void delete(long id) { MovieEntity movieEntity = crudMovieEntity.findById(id).orElse(null); if (movieEntity == null) throw new RuntimeException("Pelicula no encontrada"); crudMovieEntity.delete(movieEntity); }
Se crea el método delete en MovieService:
public void delete(long id){ this.movieRepository.delete(id); }
Finalmente, en MovieController se crea el método delete el cual responde a una petición HTTP de tipo Delete:
@DeleteMapping("/{id}") public ResponseEntity<Void> delete(@PathVariable long id) { this.movieService.delete(id); return ResponseEntity.noContent().build(); }