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