Spring Framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

284 lines
12 KiB

[[transaction-strategies]]
= Understanding the Spring Framework Transaction Abstraction
The key to the Spring transaction abstraction is the notion of a transaction strategy. A
transaction strategy is defined by a `TransactionManager`, specifically the
`org.springframework.transaction.PlatformTransactionManager` interface for imperative
transaction management and the
`org.springframework.transaction.ReactiveTransactionManager` interface for reactive
transaction management. The following listing shows the definition of the
`PlatformTransactionManager` API:
[source,java,indent=0,subs="verbatim,quotes"]
----
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 you can use it
<<transaction-programmatic-ptm, programmatically>> from your application code. Because
`PlatformTransactionManager` is an interface, it can be easily mocked or stubbed as
necessary. It is not tied to a lookup strategy, such as JNDI.
`PlatformTransactionManager` implementations are defined like any other object (or bean)
in the Spring Framework IoC container. This benefit alone makes Spring Framework
transactions a worthwhile abstraction, even when you work with JTA. You can test
transactional code much more easily than if it used JTA directly.
Again, in keeping with Spring's philosophy, the `TransactionException` that can be thrown
by any of the `PlatformTransactionManager` interface's methods is unchecked (that
is, it extends the `java.lang.RuntimeException` class). Transaction infrastructure
failures are almost invariably fatal. In rare cases where application code can actually
recover from a transaction failure, the application developer can still choose to catch
and handle `TransactionException`. The salient point is that developers are not
_forced_ to do so.
The `getTransaction(..)` method returns a `TransactionStatus` object, depending on a
`TransactionDefinition` parameter. The returned `TransactionStatus` might represent a
new transaction or can represent an existing transaction, if a matching transaction
exists in the current call stack. The implication in this latter case is that, as with
Jakarta EE transaction contexts, a `TransactionStatus` is associated with a thread of
execution.
As of Spring Framework 5.2, Spring also provides a transaction management abstraction for
reactive applications that make use of reactive types or Kotlin Coroutines. The following
listing shows the transaction strategy defined by
`org.springframework.transaction.ReactiveTransactionManager`:
[source,java,indent=0,subs="verbatim,quotes"]
----
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 reactive transaction manager is primarily a service provider interface (SPI),
although you can use it <<transaction-programmatic-rtm, programmatically>> from your
application code. Because `ReactiveTransactionManager` is an interface, it can be easily
mocked or stubbed as necessary.
The `TransactionDefinition` interface specifies:
* Propagation: Typically, all code within a transaction scope runs in
that transaction. However, you can specify the behavior if
a transactional method is run when a transaction context already exists. For
example, code can continue running in the existing transaction (the common case), or
the existing transaction can be suspended and a new transaction created. Spring
offers all of the transaction propagation options familiar from EJB CMT. To read
about the semantics of transaction propagation in Spring, see <<tx-propagation>>.
* Isolation: The degree to which this transaction is isolated from the work of other
transactions. For example, can this transaction see uncommitted writes from other
transactions?
* Timeout: How long this transaction runs before timing out and being automatically rolled back
by the underlying transaction infrastructure.
* Read-only status: You can use a read-only transaction when your code reads but
does not modify data. Read-only transactions can be a useful optimization in some
cases, such as when you use Hibernate.
These settings reflect standard transactional concepts. If necessary, refer to resources
that discuss transaction isolation levels and other core transaction concepts.
Understanding these concepts is essential to using the Spring Framework or any
transaction management solution.
The `TransactionStatus` interface provides a simple way for transactional code to
control transaction execution and query transaction status. The concepts should be
familiar, as they are common to all transaction APIs. The following listing shows the
`TransactionStatus` interface:
[source,java,indent=0,subs="verbatim,quotes"]
----
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
@Override
boolean isNewTransaction();
boolean hasSavepoint();
@Override
void setRollbackOnly();
@Override
boolean isRollbackOnly();
void flush();
@Override
boolean isCompleted();
}
----
Regardless of whether you opt for declarative or programmatic transaction management in
Spring, defining the correct `TransactionManager` implementation is absolutely essential.
You typically define this implementation through dependency injection.
`TransactionManager` implementations normally require knowledge of the environment in
which they work: JDBC, JTA, Hibernate, and so on. The following examples show how you can
define a local `PlatformTransactionManager` implementation (in this case, with plain
JDBC.)
You can define a JDBC `DataSource` by creating a bean similar to the following:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<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 related `PlatformTransactionManager` bean definition then has a reference to the
`DataSource` definition. It should resemble the following example:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
----
If you use JTA in a Jakarta EE container, then you use a container `DataSource`, obtained
through JNDI, in conjunction with Spring's `JtaTransactionManager`. The following example
shows what the JTA and JNDI lookup version would look like:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<?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 <bean/> definitions here -->
</beans>
----
The `JtaTransactionManager` does not need to know about the `DataSource` (or any other
specific resources) because it uses the container's global transaction management
infrastructure.
NOTE: The preceding definition of the `dataSource` bean uses the `<jndi-lookup/>` tag
from the `jee` namespace. For more information see
<<integration.adoc#integration.appendix.xsd-schemas-jee, The JEE Schema>>.
NOTE: If you use JTA, your transaction manager definition should look the same, regardless
of what data access technology you use, be it JDBC, Hibernate JPA, or any other supported
technology. This is due to the fact that JTA transactions are global transactions, which
can enlist any transactional resource.
In all Spring transaction setups, application code does not need to change. You can change
how transactions are managed merely by changing configuration, even if that change means
moving from local to global transactions or vice versa.
[[transaction-strategies-hibernate]]
== Hibernate Transaction Setup
You can also easily use Hibernate local transactions, as shown in the following examples.
In this case, you need to define a Hibernate `LocalSessionFactoryBean`, which your
application code can use to obtain Hibernate `Session` instances.
The `DataSource` bean definition is similar to the local JDBC example shown previously
and, thus, is not shown in the following example.
NOTE: If the `DataSource` (used by any non-JTA transaction manager) is looked up through
JNDI and managed by a Jakarta EE container, it should be non-transactional, because the
Spring Framework (rather than the Jakarta EE container) manages the transactions.
The `txManager` bean in this case is of the `HibernateTransactionManager` type. In the
same way as the `DataSourceTransactionManager` needs a reference to the `DataSource`, the
`HibernateTransactionManager` needs a reference to the `SessionFactory`. The following
example declares `sessionFactory` and `txManager` beans:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<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 use Hibernate and Jakarta EE container-managed JTA transactions, you should use the
same `JtaTransactionManager` as in the previous JTA example for JDBC, as the following
example shows. Also, it is recommended to make Hibernate aware of JTA through its
transaction coordinator and possibly also its connection release mode configuration:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<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"/>
----
Or alternatively, you may pass the `JtaTransactionManager` into your `LocalSessionFactoryBean`
for enforcing the same defaults:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<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"/>
----