0% found this document useful (0 votes)
278 views

Transaction Propagation and Isolation in Spring @transactional

The document discusses transaction propagation and isolation in Spring @Transactional. It describes the different propagation options like REQUIRED, SUPPORTS, MANDATORY, etc and how they control transaction boundaries. It also covers the various isolation levels like READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, and SERIALIZABLE and how they prevent different types of concurrency side effects. The @Transactional annotation is used to define transaction properties for methods.

Uploaded by

Lavy Stan
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
278 views

Transaction Propagation and Isolation in Spring @transactional

The document discusses transaction propagation and isolation in Spring @Transactional. It describes the different propagation options like REQUIRED, SUPPORTS, MANDATORY, etc and how they control transaction boundaries. It also covers the various isolation levels like READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, and SERIALIZABLE and how they prevent different types of concurrency side effects. The @Transactional annotation is used to define transaction properties for methods.

Uploaded by

Lavy Stan
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 11

Transaction Propagation and Isolation in

Spring @Transactional
1. Introduction
In this tutorial, we'll cover the @Transactional annotation, as well as
its isolation and propagation settings.

2. What Is @Transactional?
We can use @Transactional to wrap a method in a database transaction.
It allows us to set propagation, isolation, timeout, read-only, and rollback conditions
for our transaction. We can also specify the transaction manager.

2.1. @Transactional Implementation Details
Spring creates a proxy, or manipulates the class byte-code, to manage the creation,
commit, and rollback of the transaction. In the case of a proxy, Spring
ignores @Transactional in internal method calls.
Simply put, if we have a method like callMethod and we mark it
as @Transactional, Spring will wrap some transaction management code around the
invocation@Transactional method called:
createTransactionIfNecessary();
try {
callMethod();
commitTransactionAfterReturning();
} catch (exception) {
completeTransactionAfterThrowing();
throw exception;
}

2.2. How to Use @Transactional


We can put the annotation on definitions of interfaces, classes, or directly on
methods.  They override each other according to the priority order; from lowest to
highest we have: interface, superclass, class, interface method, superclass method, and
class method.
Spring applies the class-level annotation to all public methods of this class that
we did not annotate with @Transactional.
However, if we put the annotation on a private or protected method, Spring will
ignore it without an error.
Let's start with an interface sample:
@Transactional
public interface TransferService {
void transfer(String user1, String user2, double val);
}
Usually it's not recommended to set @Transactional on the interface; however, it is
acceptable for cases like @Repository with Spring Data. We can put the annotation on
a class definition to override the transaction setting of the interface/superclass:
@Service
@Transactional
public class TransferServiceImpl implements TransferService {
@Override
public void transfer(String user1, String user2, double val) {
// ...
}
}
Now let's override it by setting the annotation directly on the method:
@Transactional
public void transfer(String user1, String user2, double val) {
// ...
}

3. Transaction Propagation
Propagation defines our business logic's transaction boundary. Spring manages to start
and pause a transaction according to our propagation setting.
Spring calls TransactionManager::getTransaction to get or create a transaction
according to the propagation. It supports some of the propagations for all types
of TransactionManager, but there are a few of them that are only supported by
specific implementations of TransactionManager.
Let's go through the different propagations and how they work.
3.1. REQUIRED Propagation
REQUIRED is the default propagation. Spring checks if there is an active transaction,
and if nothing exists, it creates a new one. Otherwise, the business logic appends to
the currently active transaction:
@Transactional(propagation = Propagation.REQUIRED)
public void requiredExample(String user) {
// ...
}
Furthermore, since REQUIRED is the default propagation, we can simplify the code
by dropping it:
@Transactional
public void requiredExample(String user) {
// ...
}
Let's see the pseudo-code of how transaction creation works
for REQUIRED propagation:
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
return createNewTransaction();

3.2. SUPPORTS Propagation
For SUPPORTS, Spring first checks if an active transaction exists. If a transaction
exists, then the existing transaction will be used. If there isn't a transaction, it is
executed non-transactional:
@Transactional(propagation = Propagation.SUPPORTS)
public void supportsExample(String user) {
// ...
}
Let's see the transaction creation's pseudo-code for SUPPORTS:
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
return emptyTransaction;
3.3. MANDATORY Propagation
When the propagation is MANDATORY, if there is an active transaction, then it will
be used. If there isn't an active transaction, then Spring throws an exception:
@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryExample(String user) {
// ...
}
Let's again see the pseudo-code:
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
throw IllegalTransactionStateException;

3.4. NEVER Propagation
For transactional logic with NEVER propagation, Spring throws an exception if there's
an active transaction:
@Transactional(propagation = Propagation.NEVER)
public void neverExample(String user) {
// ...
}
Let's see the pseudo-code of how transaction creation works for NEVER propagation:
if (isExistingTransaction()) {
throw IllegalTransactionStateException;
}
return emptyTransaction;

3.5. NOT_SUPPORTED Propagation
If a current transaction exists, first Spring suspends it, and then the business logic is
executed without a transaction:
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedExample(String user) {
// ...
}
The JTATransactionManager supports real transaction suspension out-of-the-
box. Others simulate the suspension by holding a reference to the existing one
and then clearing it from the thread context
3.6. REQUIRES_NEW Propagation
When the propagation is REQUIRES_NEW, Spring suspends the current transaction if
it exists, and then creates a new one:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewExample(String user) {
// ...
}
Similar to NOT_SUPPORTED, we need the JTATransactionManager for actual
transaction suspension.
The pseudo-code looks like so:
if (isExistingTransaction()) {
suspend(existing);
try {
return createNewTransaction();
} catch (exception) {
resumeAfterBeginException();
throw exception;
}
}
return createNewTransaction();

3.7. NESTED Propagation
For NESTED propagation, Spring checks if a transaction exists, and if so, it marks a
save point. This means that if our business logic execution throws an exception, then
the transaction rollbacks to this save point. If there's no active transaction, it works
like REQUIRED.
DataSourceTransactionManager supports this propagation out-of-the-box. Some
implementations of JTATransactionManager may also support this.
JpaTransactionManager supports NESTED only for JDBC connections.
However, if we set the nestedTransactionAllowed flag to true, it also works for
JDBC access code in JPA transactions if our JDBC driver supports save points.
Finally, let's set the propagation to NESTED:
@Transactional(propagation = Propagation.NESTED)
public void nestedExample(String user) {
// ...
}
4. Transaction Isolation
Isolation is one of the common ACID properties: Atomicity, Consistency, Isolation,
and Durability. Isolation describes how changes applied by concurrent transactions
are visible to each other.
Each isolation level prevents zero or more concurrency side effects on a transaction:

 Dirty read: read the uncommitted change of a concurrent transaction


 Nonrepeatable read: get different value on re-read of a row if a concurrent
transaction updates the same row and commits
 Phantom read: get different rows after re-execution of a range query if another
transaction adds or removes some rows in the range and commits

We can set the isolation level of a transaction by @Transactional::isolation. It has


these five enumerations in
Spring: DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE
_READ, SERIALIZABLE.

4.1. Isolation Management in Spring


The default isolation level is DEFAULT. As a result, when Spring creates a new
transaction, the isolation level will be the default isolation of our RDBMS. Therefore,
we should be careful if we change the database.
We should also consider cases when we call a chain of methods with different
isolation. In the normal flow, the isolation only applies when a new transaction is
created. Thus, if for any reason we don't want to allow a method to execute in
different isolation, we have to
set TransactionManager::setValidateExistingTransaction to true.
Then the pseudo-code of transaction validation will be:
if (isolationLevel != ISOLATION_DEFAULT) {
if (currentTransactionIsolationLevel() != isolationLevel) {
throw IllegalTransactionStateException
}
}
Now let's get deep in different isolation levels and their effects.
4.2. READ_UNCOMMITTED Isolation
READ_UNCOMMITTED is the lowest isolation level and allows for the most
concurrent access.
As a result, it suffers from all three mentioned concurrency side effects. A transaction
with this isolation reads uncommitted data of other concurrent transactions. Also, both
non-repeatable and phantom reads can happen. Thus we can get a different result on
re-read of a row or re-execution of a range query.
We can set the isolation level for a method or class:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void log(String message) {
// ...
}
Postgres does not support READ_UNCOMMITTED isolation and falls back
to READ_COMMITED instead. Also, Oracle does not support or
allow READ_UNCOMMITTED.

4.3. READ_COMMITTED Isolation
The second level of isolation, READ_COMMITTED, prevents dirty reads.
The rest of the concurrency side effects could still happen. So uncommitted changes
in concurrent transactions have no impact on us, but if a transaction commits its
changes, our result could change by re-querying.
Here we set the isolation level:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void log(String message){
// ...
}
READ_COMMITTED is the default level with Postgres, SQL Server, and Oracle.

4.4. REPEATABLE_READ Isolation
The third level of isolation, REPEATABLE_READ, prevents dirty, and non-repeatable
reads. So we are not affected by uncommitted changes in concurrent transactions.
Also, when we re-query for a row, we don't get a different result. However, in the re-
execution of range-queries, we may get newly added or removed rows.
Moreover, it is the lowest required level to prevent the lost update. The lost update
occurs when two or more concurrent transactions read and update the same
row. REPEATABLE_READ does not allow simultaneous access to a row at all. Hence
the lost update can't happen.

Here is how to set the isolation level for a method:


@Transactional(isolation = Isolation.REPEATABLE_READ)
public void log(String message){
// ...
}
REPEATABLE_READ is the default level in Mysql. Oracle does not
support REPEATABLE_READ.

4.5. SERIALIZABLE Isolation
SERIALIZABLE is the highest level of isolation. It prevents all mentioned concurrency
side effects, but can lead to the lowest concurrent access rate because it executes
concurrent calls sequentially.
In other words, concurrent execution of a group of serializable transactions has the
same result as executing them in serial.
Now let's see how to set SERIALIZABLE as the isolation level:
@Transactional(isolation = Isolation.SERIALIZABLE)
public void log(String message){
// ...
}

 java.lang.Object

 java.lang.Enum<Propagation>

 org.springframework.transaction.annotation.Propagation

 All Implemented Interfaces:


Serializable, Comparable<Propagation>

public enum Propagation


extends Enum<Propagation>
Enumeration that represents transaction propagation behaviors for use with
the Transactional annotation, corresponding to
the TransactionDefinition interface.
Since:
1.2
Author:
Colin Sampaleanu, Juergen Hoeller

 Enum Constant Summary

Enum Constants

Enum Constant and Description

MANDATORY

Support a current transaction, throw an exception if none exists.

NESTED

Execute within a nested transaction if a current transaction exists, behave like REQUIR

NEVER

Execute non-transactionally, throw an exception if a transaction exists.

NOT_SUPPORTED

Execute non-transactionally, suspend the current transaction if one exists.

REQUIRED

Support a current transaction, create a new one if none exists.

REQUIRES_NEW

Create a new transaction, and suspend the current transaction if one exists.

SUPPORTS

Support a current transaction, execute non-transactionally if none exists.

 Enum Constant Detail

 REQUIRED

public static final Propagation REQUIRED


Support a current transaction, create a new one if none exists. Analogous
to EJB transaction attribute of the same name.
This is the default setting of a transaction annotation.

 SUPPORTS

public static final Propagation SUPPORTS


Support a current transaction, execute non-transactionally if none exists.
Analogous to EJB transaction attribute of the same name.
Note: For transaction managers with transaction
synchronization, SUPPORTS is slightly different from no transaction at all,
as it defines a transaction scope that synchronization will apply for. As a
consequence, the same resources (JDBC Connection, Hibernate Session,
etc) will be shared for the entire specified scope. Note that this depends
on the actual synchronization configuration of the transaction manager.

See Also:
AbstractPlatformTransactionManager.setTransactionSynchronization(int
)

 MANDATORY

public static final Propagation MANDATORY


Support a current transaction, throw an exception if none exists.
Analogous to EJB transaction attribute of the same name.
 REQUIRES_NEW

public static final Propagation REQUIRES_NEW


Create a new transaction, and suspend the current transaction if one
exists. Analogous to the EJB transaction attribute of the same name.
NOTE: Actual transaction suspension will not work out-of-the-box on all
transaction managers. This in particular applies
to JtaTransactionManager, which requires
the javax.transaction.TransactionManager to be made available to it
(which is server-specific in standard Java EE).

See Also:
JtaTransactionManager.setTransactionManager(javax.transaction.Transa
ctionManager)

 NOT_SUPPORTED

public static final Propagation NOT_SUPPORTED


Execute non-transactionally, suspend the current transaction if one
exists. Analogous to EJB transaction attribute of the same name.
NOTE: Actual transaction suspension will not work out-of-the-box on all
transaction managers. This in particular applies
to JtaTransactionManager, which requires
the javax.transaction.TransactionManager to be made available to it
(which is server-specific in standard Java EE).

See Also:
JtaTransactionManager.setTransactionManager(javax.transaction.Transa
ctionManager)

 NEVER

public static final Propagation NEVER


Execute non-transactionally, throw an exception if a transaction exists.
Analogous to EJB transaction attribute of the same name.
 NESTED

public static final Propagation NESTED


Execute within a nested transaction if a current transaction exists,
behave like REQUIRED otherwise. There is no analogous feature in EJB.

Note: Actual creation of a nested transaction will only work on specific


transaction managers. Out of the box, this only applies to the JDBC
DataSourceTransactionManager. Some JTA providers might support
nested transactions as well.

See Also:
DataSourceTransactionManager

You might also like