¿Dominas el uso de @Transactional en Spring?

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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");
        }
    }
}

Etiquetas: Spring SpringBoot AOP transacciones

Publicado el 10-3 21:34