En este artículo, exploraremos situaciones frecuentes en las que las transacciones en Spring pueden no funcionar correctamente. A continuación, presento 8 escenarios que pueden provocar la falla de las transacciones.
1. El motor de base de datos no soporta transacicones
Tomando MySQL como ejemplo, el motor MyISAM no admite operaciones transaccionales. Para trabajar con transacciones, generalmente se utiliza el motor InnoDB. Según la documentación oficial de MySQL, a partir de la versión 5.5.5, el motor de almacenamiento predeterminado es InnoDB, mientras que en versiones anteriores era MyISAM. Es importante verificar que el motor de base de datos utilizado soporte transacciones, de lo contrario, cualquier configuración de Spring será ineficaz.
2. La clase no es gestionada por Spring
Considere el siguiente ejemplo:
public class ProcesadorOrdenServiceImpl implements ProcesadorOrdenService{
@Transactional
public void actualizarOrden(Orden orden){
//actualizar orden
}
}
Si se comenta la anotación @Service, esta clase no será cargada como un Bean y, por lo tanto, no será gestionada por Spring, lo que causa que la transacción falle.
3. El método no es público
La anotación @Transactional solo es efectiva en métodos públicos. Si se desea utilizar en métodos no públicos, es necesario habilitar el modo de proxy estático basado en el framework AspectJ.
4. Auto-invocación de métodos
Considere este ejemplo:
@Service
public class ProcesadorOrdenServiceImpl implements ProcesadorOrdenService {
public void procesar(Orden orden) {
actualizarOrden(orden);
}
}
@Transactional
public void actualizarOrden(Orden orden) {
// actualizar orden
}
}
El método procesar no tiene la anotación @Transactional. Si llama al método actualizarOrden que sí tiene la anotación, ¿la transacción en actualizarOrden funcionará?
Ahora observe este otro ejemplo:
@Service
public class ProcesadorOrdenServiceImpl implements ProcesadorOrdenService {
@Transactional
public void procesar(Orden orden) {
actualizarOrden(orden);
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void actualizarOrden(Orden orden) {
actualizarOrden(orden);
}
}
En este caso, el método procesar tiene @Transactional y actualizarOrden tiene Propagation.REQUIRES_NEW. Si se inicia una nueva transacción, ¿esta funcionará correctamente?
En ambos ejemplos, las transacciones no funcionan debido a la auto-invocación. Cuando un método de la misma clase llama a otro método anotado con @Transactional, Spring no puede interceptar la llamada a través del proxy. Por defecto, solo las llamadas a métodos del proxy externo de Spring activan las transacciones.
Una solución es inyectar la clase en sí misma y usar el objeto inyectado para llamar al otro método, aunque esta solución no es muy elegante. En Spring, se puede exponer y obtener el proxy actual del hilo actual. Para habilitar la exposición del proxy, agregue la siguiente anotación en la clase de inicio:
@EnableAspectJAutoProxy(exposeProxy = true)
Luego, obtenga el proxy actual y llame al método transaccional:
((ProcesadorOrdenService) AopContext.currentProxy()).actualizarOrden();
Spring solo activa transacciones al llamar a métodos públicos del proxy de Spring.
5. No se ha configurado el gestor de transacciones
Si no se configura el siguiente gestor de transacciones DataSourceTransactionManager, las transacciones no funcionarán:
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
Sin embargo, en Spring Boot, al incluir la dependencia del starter spring-boot-starter-data-jdbc, el gestor de transacciones DataSourceTransactionManager se configura automáticamente. Por lo tanto, este problema no existe en Spring Boot, pero es importante en el framework Spring tradicional.
6. Configuración de propagación que no soporta transacciones
Considere el siguiente ejemplo:
@Service
public class ProcesadorOrdenServiceImpl implements ProcesadorOrdenService {
@Transactional
public void procesar(Orden orden) {
actualizarOrden(orden);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void actualizarOrden(Orden orden) {
//actualizar orden
}
}
Aquí, Propagation.NOT_SUPPORTED indica que el método no se ejecuta en modo transaccional. Si existe una transacción actual, se suspende. Esta configuración explícitamente deshabilita la ejecución transaccional.
7. La excepción no es lanzada
Considere este ejemplo:
@Service
public class ProcesadorOrdenServiceImpl implements ProcesadorOrdenService {
@Transactional
public void procesar(Orden orden) {
try{
// procesar orden
}catch(Exception e){
// Manejo de excepción sin relanzar
}
}
}
Este método captura la excepción pero no la relanza, por lo que la transacción no se revertirá. Solo cuando se lance una excepción, la transacción se revertirá.
8. El tipo de excepción no coincide
Considere este ejemplo:
@Service
public class ProcesadorOrdenServiceImpl implements ProcesadorOrdenService {
@Transactional
public void procesar(Orden orden) {
try{
// procesar orden
}catch(Exception e){
throw new Exception("Fallo en el procesamiento");
}
}
}
Spring reverte por defecto las excepciones de tipo RuntimeException, pero la excepción lanzada es de tipo Exception, por lo que no coinciden y la transacción no se revertirá. Para activar el rollback de excepciones que no son RuntimeException, es necesario especificar las clases de excepción en la anotación @Transactional:
@Transactional(rollbackFor = Exception.class)
En este artículo, hemos resumido varios escenarios comunes que pueden causar la falla de las transacciones cuando se utiliza la anotación @Transactional. Si una transacción no funciona, estos son los aspectos clave a verificar. Los problemas más frecuentes suelen ser la auto-invocación de métodos, la captura de excepciones sin relanzar, y la falta de coincidencia en los tipos de excepción lanzadas.