Cuando se utiliza la anotación @Transactional en Spring, el framework configura un proxy AOP para gestionar las transacciones antes y después de la ejecución del método anotado. Este proceso implica:
- Intercepción del método: El proxy intercepta la llamada al método y gestiona la transacción según las configuraciones especificadas, como el comportamiento de propagación y el nivel de aislamiento.
- Gestión de transacciones: Antes de la ejecución del método, el proxy inicia una nueva transacción; si el método se completa con éxito, se confirma la transacción; si se lanza una excepción, se revierte la transacción.
En resumen, cuando se invoca un método anotado con @Transactional a través de su clase proxy, Spring administra automáticamente la transacción.
Cuándo falla @Transactional?
Aunque la anotación es útil, hay casos en los que puede no funiconar como se espera:
- Llamadas internas dentro de la misma clase: Si un método anotado con @Transactionla es invocado por otro método en la misma clase, la transacción no se aplica porque la llamada se realiza directamente sin pasar por el proxy.
- No se especifica rollbackFor: Por defecto, solo se revertirá la transacción para RuntimeException y Error. Para otros tipos de excepciones, como IOException, debe especificarse explícitamente en rollbackFor.
- Captura de excepciones: Si se captura una excepción dentro de un método anotado y no se vuelve a lanzar, la transacción no se revertirá porque la excepción no se propaga al gestor de transacciones de Spring.
- Métodos privados o finales: No se pueden aplicar transacciones a métodos privados, finales o estáticos porque Spring no puede crear un proxy para ellos.
- Comportamiento de propagación: Si un método anotado llama a otro método con una configuración de propagación que no admite la transacción actual, esta última no se aplicará.
Ejemplos de fallos y soluciones
Llaamdas internas dentro de la misma clase
Considera el siguiente ejemplo donde metodo1 llama a metodo2:
@Service
public class MiServicio {
private void metodo1() {
metodo2();
// ...
}
@Transactional
public void metodo2() {
// ...
}
}
Para resolver esto, puedes mover metodo2 a otra clase:
@Service
public class MiServicio {
@Autowired
private MiAdministrador miAdministrador;
private void metodo1() {
miAdministrador.metodo2();
// ...
}
}
@Service
public class MiAdministrador {
@Transactional
public void metodo2() {
// ...
}
}
Otra opción es obtener el proxy de la clase actual mediante AopContext:
@Service
public class MiServicio {
private void metodo1() {
((MiServicio) AopContext.currentProxy()).metodo2();
// ...
}
@Transactional
public void metodo2() {
// ...
}
}
Recuerda habilitar la exposición del proxy en la clase principal de la aplicación:
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
public class AplicacionPrincipal {
public static void main(String[] args) {
SpringApplication.run(AplicacionPrincipal.class, args);
}
}
No se especifica rollbackFor
Si deseas revertir la transacción para cualquier tipo de excepción, especifica rollbackFor = Exception.class:
@Service
public class MiServicio {
private void metodo1() {
metodo2();
// ...
}
@Transactional(rollbackFor = Exception.class)
public void metodo2() {
// ...
}
}
Captura de excepciones
Si una excepción es capturada pero no se vuelve a lanzar, la transacción no se revertirá:
@Service
@Slf4j
public class ServicioUsuario {
@Transactional
public void registrarUsuario(String nombreUsuario) {
try {
// ...
if (nombreUsuario == null) {
throw new IllegalArgumentException("El nombre de usuario no puede ser nulo");
}
} catch (Exception e) {
log.error("Excepción capturada: " + e.getMessage());
}
}
}
Para asegurar la reversión de la transacción, vuelve a lanzar la excepción:
@Service
@Slf4j
public class ServicioUsuario {
@Transactional
public void registrarUsuario(String nombreUsuario) {
try {
// ...
if (nombreUsuario == null) {
throw new IllegalArgumentException("El nombre de usuario no puede ser nulo");
}
} catch (Exception e) {
log.error("Excepción: " + e.getMessage());
throw new ExcepcionNegocio("Excepción de negocio");
}
}
}