La clave para comprender la abstracción de transacciones de Spring es el concepto de estrategia de transacción. La estrategia de transacción está determinada por TransactionManager, en particular por la interfaz org.springframework.transaction.PlatformTransactionManager para gestionar transacciones imperativas, así como por org.springframework.transaction.ReactiveTransactionManager interfaz para la gestión de transacciones reactivas. La siguiente lista muestra la definición de API PlatformTransactionManager:

public interface ReactiveTransactionManager extends TransactionManager {
    Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;
    Mono<Void> commit(ReactiveTransaction status) throws TransactionException;
    Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
}

Esta es principalmente una interfaz de proveedor de servicios (SPI), aunque se puede utilizar programáticamente desde el código de la aplicación. Debido a que PlatformTransactionManager es una interfaz, se puede implementar fácilmente como un objeto simulado o una función auxiliar según sea necesario. No está vinculado a una estrategia de búsqueda como JNDI. Las implementaciones de PlatformTransactionManager se definen como cualquier otro objeto (o bean) en el contenedor Spring Framework IoC. Este beneficio por sí solo hace que las transacciones desde Spring Framework sean una abstracción decente, incluso si está trabajando con JTA. Probar el código transaccional se vuelve mucho más fácil que si se usara JTA directamente.

Nuevamente, de acuerdo con la filosofía Spring, cualquiera de los puede generar una excepción TransactionException métodos de interfaz PlatformTransactionManager, no se puede verificar (es decir, extiende la clase java.lang.RuntimeException). Las fallas en la infraestructura de transacciones casi siempre son críticas. En el raro caso de que el código de la aplicación pueda realmente recuperarse de una falla en la transacción, el desarrollador de la aplicación aún puede interceptar y manejar TransactionException. El punto importante es que los desarrolladores pueden hacer esto sin coerción.

El método getTransaction(..) devuelve un TransactionStatus objeto dependiendo del parámetro TransactionDefinition. El TransactionStatus devuelto puede representar una nueva transacción o puede representar una transacción existente si existe una transacción correspondiente en la pila de llamadas actual. En el último caso, al igual que con los contextos de transacción Java EE, TransactionStatus está asociado con el hilo de ejecución.

A partir de Spring Framework 5.2, Spring también proporciona una abstracción de gestión de transacciones para reactivos. aplicaciones que utilizan tipos reactivos o corrutinas de Kotlin. La siguiente lista muestra la estrategia de transacción definida por org.springframework.transaction.ReactiveTransactionManager:

public interface ReactiveTransactionManager extends TransactionManager {
    Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;
    Mono<Void> commit(ReactiveTransaction status) throws TransactionException;
    Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
}

La interfaz TransactionDefinition define:

  • Distribución: normalmente, todo el código en el alcance de una transacción se ejecuta en esa transacción. Sin embargo, puede configurar la lógica para que funcione en los casos en que se ejecuta un método transaccional cuando ya existe un contexto transaccional. Por ejemplo, la ejecución del código puede continuar en una transacción existente (el caso habitual), o se puede suspender una transacción existente y crear una nueva transacción. Spring ofrece todas las opciones de propagación de transacciones familiares de CMT de EJB. Para obtener una descripción general de la semántica de propagación de transacciones en Spring, consulte "Transaction Propagation".

  • Aislamiento: el grado en que una transacción determinada está aislada de la operación de otras transacciones. Por ejemplo, ¿esta transacción puede ver registros no confirmados de otras transacciones?

  • Tiempo de espera: el tiempo que tarda una transacción en completarse antes de completarse y revertirse automáticamente mediante el proceso de la transacción. infraestructura subyacente.

  • Estado de solo lectura: puede utilizar una transacción en modo de solo lectura, en el que su código leerá pero no modificará los datos. Las transacciones de solo lectura pueden ser útiles en un contexto de optimización en algunos casos, como si está utilizando Hibernate.

Estas opciones reflejan conceptos de transacciones estándar. Si es necesario, consulte los recursos que analizan los niveles de aislamiento de transacciones y otros conceptos básicos de transacciones. Comprender estos conceptos es esencial para utilizar Spring Framework o cualquier solución de gestión de transacciones.

La interfaz TransactionStatus permite que el código transaccional monitoree sin problemas la ejecución de una transacción y consulte el estado de la transacción. Los conceptos deberían resultarle familiares, ya que son comunes a todas las API de transacciones. La siguiente lista muestra la interfaz TransactionStatus:

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
    @Override
    boolean isNewTransaction();
    boolean hasSavepoint();
    @Override
    void setRollbackOnly();
    @Override
    boolean isRollbackOnly();
    void flush();
    @Override
    boolean isCompleted();
}

Ya sea que elija administrar transacciones en Spring de forma declarativa o programática, determinar la implementación correcta de TransactionManager es absolutamente necesario. Normalmente, esta implementación se define mediante inyección de dependencia.

TransactionManager las implementaciones generalmente requieren conocimiento del entorno en el que operan: JDBC, JTA, Hibernate, etc. Los siguientes ejemplos muestran cómo se puede definir una implementación local de PlatformTransactionManager (en este caso utilizando JDBC normal).

Puede definir un JDBC DataSource creando un bean, similar al siguiente:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

La definición de bean PlatformTransactionManager asociada se hace referencia a la definición de DataSource. La definición debería ser similar al siguiente ejemplo:

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

Si está utilizando JTA en un contenedor Java EE, entonces está utilizando el contenedor DataSource obtenido a través de JNDI, combinado con JtaTransactionManager de Spring. El siguiente ejemplo muestra cómo se vería la versión de búsqueda a través de JTA y JNDI:

<?xml version="1.0" encoding="UTF-8"?>
            <beans xmlns="http://www.springframework.org/schema/beans"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xmlns:jee="http://www.springframework.org/schema/jee"
                   xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jee
        https://www.springframework.org/schema/jee/spring-jee.xsd">
    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>
    <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
    <!-- otras definiciones <bean/--> -->
</beans>

JtaTransactionManager no necesita conocer DataSource (ni ningún otro recursos específicos) porque utiliza la infraestructura global de gestión de transacciones del contenedor.

La definición anterior del bean dataSource utiliza el <jndi-lookup/> del espacio de nombres jee. Para obtener más detalles, consulte Esquema JEE.
Si está utilizando JTA, la definición del administrador de transacciones debe verse igual sin importar qué tecnología de acceso a datos se utilice, ya sea JDBC, Hibernate vía JPA o cualquier otra tecnología compatible. Esto se debe a que las transacciones JTA son transacciones globales que pueden utilizar cualquier recurso transaccional.

Para cualquier conjunto de valores especificados para las transacciones Spring, no es necesario cambiar el código de la aplicación. Puede cambiar la forma en que se administran las transacciones simplemente cambiando la configuración, incluso si el cambio significa pasar de transacciones locales a globales o viceversa.

Configuración de transacciones de Hibernate

También puede utilizar fácilmente transacciones locales de Hibernate, como lo mostrarán los siguientes ejemplos. En este caso, necesita definir un LocalSessionFactoryBean para Hibernate que el código de su aplicación pueda usar para obtener instancias de Session de Hibernate.

Definir un bean DataSource es similar al ejemplo de JDBC local demostrado anteriormente y, por lo tanto, no se muestra en el siguiente ejemplo.

Si la fuente de datos la búsqueda es DataSource (utilizada por cualquier administrador de transacciones que no sea JTA) se realiza a través de JNDI y se administra a través de Java EE; debe ser no transaccional porque Spring Framework (no el contenedor Java EE) administra las transacciones.

En este caso el bean txManager es de tipo HibernateTransactionManager. Así como DataSourceTransactionManager requiere una referencia a DataSource, HibernateTransactionManager requiere una referencia a SessionFactory. El siguiente ejemplo declara los beans sessionFactory y txManager:

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mappingResources">
        <list>
            <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <value>
            hibernate.dialect=${hibernate.dialect}
        </value>
    </property>
</bean>
<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

Si está utilizando transacciones JTA administradas por contenedor de Hibernate y Java EE, debe utilizar el mismo JtaTransactionManager como en el ejemplo anterior con JTA para JDBC, como se muestra en el siguiente ejemplo. También se recomienda hacer que Hibernate aprenda sobre JTA a través de su coordinador de transacciones y posiblemente la configuración del modo de liberación de conexión:

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mappingResources">
        <list>
            <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <value>
            hibernate.dialect=${hibernate.dialect}
            hibernate.transaction.coordinator_class=jta
            hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
        </value>
    </property>
</bean>
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

O puedes pasar taTransactionManager a su LocalSessionFactoryBean para proporcionar los mismos valores predeterminados:

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mappingResources">
        <list>
            <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <value>
            hibernate.dialect=${hibernate.dialect}
        </value>
    </property>
    <property name="jtaTransactionManager" ref="txManager"/>
</bean>
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>