CodeGym /Cursos /Módulo 5. Spring /Control programático de transacciones: TransactionTemplat...

Control programático de transacciones: TransactionTemplate

Módulo 5. Spring
Nivel 6 , Lección 4
Disponible

Cuando hablamos de control programático de transacciones, nos referimos al control directo de las transacciones en el código. A diferencia del enfoque declarativo, donde confiamos en la magia de Spring y en la anotación @Transactional, aquí nosotros controlamos explícitamente qué, cómo y cuándo ocurre con nuestra transacción.

Veamos los escenarios principales en los que tiene sentido el control programático de transacciones:

  1. Flexibilidad. A veces tenemos lógica compleja de transacciones que es difícil de expresar con declaraciones.
  2. Transacción parcial. Necesitas ejecutar parte de las operaciones dentro de una transacción y otras fuera de ella.
  3. Optimización. No hace falta crear una transacción para cada método, y el control manual permite evitar sobrecarga innecesaria.

Y si ese es el caso, entonces te viene al rescate TransactionTemplate.


¿Qué es TransactionTemplate?

TransactionTemplate es una clase de Spring que facilita el control programático de transacciones. Proporciona una API que permite envolver la lógica de negocio en transacciones, sin obligarnos a bajar al manejo de APIs de bajo nivel (por ejemplo, Connection o TransactionManager).

Estas son las ventajas principales de TransactionTemplate:

  • Facilidad de uso: no hay que abrir/cerrar transacciones manualmente.
  • Minimiza errores: Spring se encarga de las tareas de infraestructura, dejando al desarrollador solo la lógica de negocio.
  • Configuración sencilla: puedes definir reglas de transacción (por ejemplo, aislamiento, timeouts y demás) directamente en el código.

Cómo usar TransactionTemplate

Antes de entrar en detalles, veamos un ejemplo:


@Component
public class TransactionalService {

    private final TransactionTemplate transactionTemplate;

    public TransactionalService(PlatformTransactionManager transactionManager) {
        // Inicializamos TransactionTemplate usando TransactionManager
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public void executeTransactionalLogic() {
        transactionTemplate.execute(status -> {
            // Dentro de esta transacción ejecutamos la lógica de negocio
            performDatabaseOperation1();
            performDatabaseOperation2();

            // Si hace falta, se puede forzar un rollback manualmente
            // status.setRollbackOnly();

            return null; // Devolvemos el resultado (puedes devolver cualquier objeto o null)
        });
    }

    private void performDatabaseOperation1() {
        // Lógica de la primera operación con la base de datos
    }

    private void performDatabaseOperation2() {
        // Lógica de la segunda operación con la base de datos
    }
}

En el código:

  1. Inicialización TransactionTemplate. Para trabajar con TransactionTemplate nos hace falta un PlatformTransactionManager (por ejemplo, JpaTransactionManager, si trabajas con JPA/Hibernate). Lo pasamos al constructor del servicio y creamos una instancia de TransactionTemplate.
  2. Método execute. Todo el código que queramos ejecutar dentro de la transacción lo envolvemos en la llamada transactionTemplate.execute(). Este método recibe como argumento un TransactionCallback, implementado como una lambda. Todo lo que esté dentro de la lambda se ejecuta dentro de la transacción.
  3. Rollbacks de la transacción. Si ocurre un error dentro de la lógica transaccional, Spring hará el rollback automáticamente. Si queremos forzar el rollback manualmente, podemos llamar a status.setRollbackOnly().

Configuración de TransactionTemplate

Aislamiento, timeouts y readOnly

Al igual que con la anotación @Transactional, podemos establecer configuraciones de transacción mediante TransactionTemplate. Ejemplo:


@Transactional
public class ConfigurableTransactionalService {

    private final TransactionTemplate transactionTemplate;

    public ConfigurableTransactionalService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);

        // Establecemos el aislamiento
        this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);

        // Timeout de la transacción (en segundos)
        this.transactionTemplate.setTimeout(5);

        // Transacción solo de lectura
        this.transactionTemplate.setReadOnly(true);
    }

    public void executeReadOnlyTransaction() {
        transactionTemplate.execute(status -> {
            performReadOperation();
            return null;
        });
    }

    private void performReadOperation() {
        // Transacción para operaciones de lectura
    }
}

Detalles:

  • setIsolationLevel. Configura el nivel de aislamiento para la transacción (por ejemplo, ISOLATION_READ_COMMITTED, ISOLATION_SERIALIZABLE, etc.). Esto es importante para manejar consultas concurrentes.
  • setTimeout. Define cuánto puede durar la transacción antes de que se haga el rollback automático.
  • setReadOnly. Ayuda a optimizar el rendimiento si la transacción es solo de lectura de datos.

Ejemplo: transaccionalidad parcial

Imagina que estás desarrollando una tienda online y necesitas:

  1. Crear el pedido dentro de una transacción.
  2. Enviar una notificación por email al usuario fuera de la transacción (para que un proceso largo no bloquee la base de datos).

Así es como se puede hacer con TransactionTemplate:


@Component
public class OrderService {

    private final TransactionTemplate transactionTemplate;
    private final EmailService emailService;

    public OrderService(PlatformTransactionManager transactionManager, EmailService emailService) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
        this.emailService = emailService;
    }

    public void placeOrder(Order order) {
        transactionTemplate.execute(status -> {
            // Operaciones con la base de datos dentro de la transacción
            saveOrder(order);
            updateInventory(order);

            return null;
        });

        // Operaciones fuera de la transacción
        emailService.sendOrderConfirmation(order.getEmail());
    }

    private void saveOrder(Order order) {
        // Lógica para guardar el pedido
    }

    private void updateInventory(Order order) {
        // Lógica para actualizar el inventario
    }
}

Problemas y errores al usar TransactionTemplate

Error: no se produce el rollback. Si dentro del código transaccional ocurre una excepción, asegúrate de que esa excepción sea unchecked (subclase de RuntimeException). Spring por defecto no hace rollback para excepciones checked. Esto se puede configurar con la anotación @Transactional, indicando rollbackFor, o llamando manualmente a status.setRollbackOnly().

Error: una transacción read-only modifica datos. Si has establecido setReadOnly(true), y luego haces una escritura en la base, Spring puede que no rechace los cambios (depende de la base de datos). Usa readOnly solo para transacciones claramente "de lectura".


Práctica: usar TransactionTemplate en una aplicación real

Añadamos control programático de transacciones a nuestra app. Supongamos que tenemos un sistema de gestión de biblioteca y necesitamos:

  1. Registrar un nuevo usuario.
  2. Al mismo tiempo, asignarle su primer libro al usuario.

Ejemplo:


@Service
public class LibraryService {

    private final TransactionTemplate transactionTemplate;
    private final UserRepository userRepository;
    private final BookRepository bookRepository;

    public LibraryService(PlatformTransactionManager transactionManager,
                          UserRepository userRepository,
                          BookRepository bookRepository) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
        this.userRepository = userRepository;
        this.bookRepository = bookRepository;
    }

    public void registerUserAndIssueBook(User user, Book book) {
        transactionTemplate.execute(status -> {
            userRepository.save(user); // Guardamos al usuario
            book.setIssuedTo(user);    // Actualizamos el estado del libro
            bookRepository.save(book); // Guardamos los cambios del libro

            return null;
        });
    }
}

¿Cuándo usar TransactionTemplate?

Entonces, ¿cuándo conviene elegir TransactionTemplate en lugar de @Transactional?

  • Necesitas más control sobre la transacción — ejecutar parte de la lógica fuera de la transacción.
  • Estás escribiendo una librería o framework y quieres hacer la gestión de transacciones genérica.
  • Tus transacciones son complejas y requieren configuración manual (por ejemplo, rollback parcial).

Para la mayoría de los casos basta la gestión declarativa con @Transactional, pero siempre viene bien tener a mano una herramienta como TransactionTemplate para escenarios complejos.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION