@Transactional y rollback en Spring Boot

Clase 25 de 31Curso de Java: Backend con Spring Boot

Contenido del curso

JPA con Spring y Spring Data

Resumen

Controlar lo que sucede cuando una operación de base de datos falla es fundamental en cualquier aplicación profesional. La anotación @Transactional de Spring permite hacer rollback automático de todas las transacciones registradas cuando ocurre un error, evitando datos inconsistentes. A continuación se explica paso a paso cómo implementarla junto con la capa de servicio.

¿Cómo crear una clase de servicio con @Service?

Antes de trabajar con transacciones, es necesario separar la lógica de negocio en una clase dedicada. En Spring Boot, el estereotipo @Service marca una clase como componente de servicio, lo que permite que el framework la gestione e inyecte automáticamente [1:05].

Dentro de la carpeta service se crea la clase UserService anotada con @Service. Lo primero es configurar un log utilizando Apache Commons Logging para registrar información durante la ejecución:

java @Service public class UserService { private final Log log = LogFactory.getLog(UserService.class); private final UserRepository userRepository;

public UserService(UserRepository userRepository) { this.userRepository = userRepository; }

}

La inyección de dependencias se realiza a nivel de constructor, pasando el UserRepository como parámetro. Este patrón garantiza que la dependencia esté disponible desde el momento en que se instancia el servicio [1:52].

¿Qué hace la anotación @Transactional y cómo se aplica?

El método saveTransactional recibe una lista de usuarios y los almacena uno por uno. Primero se utiliza un stream con peek para registrar cada usuario en el log, y luego un forEach para persistirlos mediante el repositorio [2:30].

java @Transactional public void saveTransactional(List<User> users) { users.stream() .peek(user -> log.info("Usuario insertado: " + user)) .forEach(userRepository::save); }

El operador :: es una referencia a método de Java 8. En lugar de escribir una lambda como user -> userRepository.save(user), se usa userRepository::save, que tiene exactamente la misma funcionalidad pero resulta más conciso [3:18].

Al colocar @Transactional sobre el método, Spring envuelve toda la ejecución en una sola transacción de base de datos. Si el usuario uno y el usuario dos se registran correctamente pero el usuario tres produce un error, ninguno de los registros anteriores se conserva. El rollback revierte todos los cambios realizados dentro de esa transacción [3:45].

¿Cómo obtener todos los usuarios con findAll?

Para verificar qué datos quedaron almacenados, se agrega un segundo método en UserService:

java public List<User> getAllUsers() { return userRepository.findAll(); }

El método findAll proviene de la interfaz JpaRepository y devuelve una lista genérica de tipo T, es decir, cualquier entidad mapeada. No requiere implementación adicional [6:20].

¿Cómo probar el flujo transaccional en la aplicación?

En la clase FundamentosApplication se crea un método privado saveWithErrorTransactional donde se instancian cuatro usuarios de prueba [4:40]:

java User test1 = new User("TestTransactional1", "testtransactional1@domain.com", LocalDate.now()); User test2 = new User("TestTransactional2", "testtransactional2@domain.com", LocalDate.now()); User test3 = new User("TestTransactional3", "testtransactional3@domain.com", LocalDate.now()); User test4 = new User("TestTransactional4", "testtransactional4@domain.com", LocalDate.now());

List<User> users = Arrays.asList(test1, test2, test3, test4); userService.saveTransactional(users);

Después de guardar, se consultan todos los usuarios y se imprimen con un stream y forEach para confirmar el resultado [7:10]:

java userService.getAllUsers() .stream() .forEach(user -> log.info("Usuario dentro del método transaccional: " + user));

Al ejecutar sin provocar errores, los cuatro registros aparecen correctamente en la base de datos. La verdadera ventaja de @Transactional se evidencia cuando un fallo interrumpe el proceso: todos los inserts previos se revierten automáticamente.

¿Por qué es importante separar repositorio y servicio?

  • El repositorio se encarga exclusivamente del acceso a datos.
  • El servicio contiene la lógica de negocio y coordina las operaciones.
  • Esta separación facilita el mantenimiento, las pruebas unitarias y la reutilización de código.

Ahora que conoces la estructura básica de @Transactional y la capa de servicio, vale la pena experimentar provocando errores intencionados para observar el rollback en acción. ¿Has tenido algún caso donde datos inconsistentes llegaron a producción? Comparte tu experiencia en los comentarios.