The key to understanding Spring's transaction abstraction is the concept of transaction strategy. The transaction strategy is determined by the TransactionManager, in particular by the org.springframework.transaction.PlatformTransactionManager interface for managing imperative transactions, as well as the org.springframework.transaction.ReactiveTransactionManager interface for managing reactive transactions . The following listing shows the PlatformTransactionManager API definition:


public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}

This is primarily a Service Provider Interface (SPI), although it can be used programmatically from the application code. Because PlatformTransactionManager is an interface, it can easily be implemented as a mock object or stub function as needed. It is not tied to a search strategy like JNDI. PlatformTransactionManager implementations are defined like any other object (or bean) in the Spring Framework IoC container. This benefit alone makes transactions from the Spring Framework a decent abstraction, even if you're working with JTA. Testing transactional code becomes much easier than if it were to use JTA directly.

Again, in keeping with the Spring philosophy, a TransactionException exception can be thrown by any of the PlatformTransactionManager interface methods is unverifiable (that is, it extends the java.lang.RuntimeException class). Transaction infrastructure failures are almost always critical. In the rare case where application code can actually recover from a transaction failure, the application developer can still intercept and handle TransactionException. The important point is that developers can do this without coercion.

The getTransaction(..) method returns a TransactionStatus object depending on the TransactionDefinition parameter. The returned TransactionStatus may represent a new transaction, or may represent an existing transaction if a corresponding transaction exists in the current call stack. In the latter case, as with Java EE transaction contexts, TransactionStatus is associated with the thread of execution.

Starting with Spring Framework 5.2, Spring also provides a transaction management abstraction for reactive applications using reactive types or Kotlin coroutines. The following listing shows the transaction strategy defined by 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;
}

The TransactionDefinition interface defines:

  • Distribution: Typically, all code in the scope of a transaction runs in that transaction. However, you can set the logic to work in cases where a transactional method is run when a transactional context already exists. For example, code execution may continue in an existing transaction (the usual case), or an existing transaction may be suspended and a new transaction created. Spring offers all the transaction propagation options familiar from CMT from EJB. For an overview of transaction propagation semantics in Spring, see "Transaction Propagation".

  • Isolation: The degree to which a given transaction is isolated from the operation of other transactions. For example, can this transaction see uncommitted records from other transactions?

  • Wait Time: The length of time a transaction takes to complete before it is completed and automatically rolled back by the transaction's underlying infrastructure.

  • Read-only status: You can use a transaction in read-only mode, in which your code will read but not modify the data. Read-only transactions can be useful in an optimization context in some cases, such as if you are using Hibernate.

These options reflect standard transaction concepts. If necessary, refer to resources that discuss transaction isolation levels and other basic transaction concepts. Understanding these concepts is essential to using the Spring Framework or any transaction management solution.

The TransactionStatus interface allows transactional code to seamlessly monitor the execution of a transaction and query the transaction status. The concepts should be familiar as they are common to all transaction APIs. The following listing shows the TransactionStatus interface:


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

Whether you choose to manage transactions in Spring declaratively or programmatically, determining the correct TransactionManager implementation is absolutely necessary. Typically this implementation is defined through dependency injection.

TransactionManager implementations usually require knowledge of the environment in which they operate: JDBC, JTA, Hibernate, and so on. The following examples show how you can define a local PlatformTransactionManager implementation (in this case using regular JDBC).

You can define a DataSource JDBC by creating a bean , similar to the following:


<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>

The associated PlatformTransactionManager bean definition is then referenced to the DataSource definition. The definition should look similar to the following example:


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

If you are using JTA in a Java EE container, then you are using the container DataSource obtained through JNDI, combined with JtaTransactionManager from Spring. The following example shows what the search version via JTA and JNDI would look like:


<?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" />
    <!-- other definitions <bean/--> -->
</beans>

JtaTransactionManager does not need to know about DataSource (or any other specific resources ) because it uses the container's global transaction management infrastructure.

The previous definition of the dataSource bean uses the <jndi-lookup/> from the jee namespace. For more details see JEE Scheme.
If you are using JTA, the transaction manager definition should look the same no matter what data access technology is used, be it JDBC, Hibernate via JPA, or any other supported technology. This is because JTA transactions are global transactions that can use any transactional resource.

For any set of values specified for Spring transactions, the application code does not need to be changed. You can change the way transactions are managed simply by changing the configuration, even if the change means moving from local to global transactions or vice versa.

Configuring Hibernate transactions

You can also easily use local transactions from Hibernate, as the following examples will show. In this case, you need to define a LocalSessionFactoryBean for Hibernate that your application code can use to obtain Session instances from Hibernate.

Defining a DataSource is similar to the local JDBC example demonstrated earlier and is therefore not shown in the following example.

If the data source search is DataSource (used by any non-JTA transaction manager) is done through JNDI and managed through Java EE, it must be non-transactional because the Spring Framework (not the Java EE container) manages transactions.

In this case the txManager bean is of type HibernateTransactionManager. Just as DataSourceTransactionManager requires a reference to DataSource, HibernateTransactionManager requires a reference to SessionFactory. The following example declares the sessionFactory and txManager beans:


<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>

If you are using Hibernate and Java EE container-managed JTA transactions, you should use the same JtaTransactionManager as and in the previous example with JTA for JDBC, as shown in the following example. It is also recommended to make Hibernate learn about JTA through its transaction coordinator and possibly connection release mode configuration:


<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>

Or you can pass JtaTransactionManager to your LocalSessionFactoryBean to provide the same defaults:


<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"/>