[java] Spring @Transactional - isolation, propagation

Can someone explain what isolation & propagation parameters are for in the @Transactional annotation via real-world example?

Basically when and why I should choose to change their default values.

This question is related to java spring transactional isolation propagation

The answer is


Good question, although not a trivial one to answer.

Propagation

Defines how transactions relate to each other. Common options:

  • REQUIRED: Code will always run in a transaction. Creates a new transaction or reuses one if available.
  • REQUIRES_NEW: Code will always run in a new transaction. Suspends the current transaction if one exists.

Isolation

Defines the data contract between transactions.

  • ISOLATION_READ_UNCOMMITTED: Allows dirty reads.
  • ISOLATION_READ_COMMITTED: Does not allow dirty reads.
  • ISOLATION_REPEATABLE_READ: If a row is read twice in the same transaction, the result will always be the same.
  • ISOLATION_SERIALIZABLE: Performs all transactions in a sequence.

The different levels have different performance characteristics in a multi-threaded application. I think if you understand the dirty reads concept you will be able to select a good option.


Example of when a dirty read can occur:

  thread 1   thread 2      
      |         |
    write(x)    |
      |         |
      |        read(x)
      |         |
    rollback    |
      v         v 
           value (x) is now dirty (incorrect)

So a sane default (if such can be claimed) could be ISOLATION_READ_COMMITTED, which only lets you read values which have already been committed by other running transactions, in combination with a propagation level of REQUIRED. Then you can work from there if your application has other needs.


A practical example of where a new transaction will always be created when entering the provideService routine and completed when leaving:

public class FooService {
    private Repository repo1;
    private Repository repo2;

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void provideService() {
        repo1.retrieveFoo();
        repo2.retrieveFoo();
    }
}

Had we instead used REQUIRED, the transaction would remain open if the transaction was already open when entering the routine. Note also that the result of a rollback could be different as several executions could take part in the same transaction.


We can easily verify the behaviour with a test and see how results differ with propagation levels:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:/fooService.xml")
public class FooServiceTests {

    private @Autowired TransactionManager transactionManager;
    private @Autowired FooService fooService;

    @Test
    public void testProvideService() {
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        fooService.provideService();
        transactionManager.rollback(status);
        // assert repository values are unchanged ... 
}

With a propagation level of

  • REQUIRES_NEW: we would expect fooService.provideService() was NOT rolled back since it created it's own sub-transaction.

  • REQUIRED: we would expect everything was rolled back and the backing store was unchanged.


We can add for this:

@Transactional(readOnly = true)
public class Banking_CustomerService implements CustomerService {

    public Customer getDetail(String customername) {
        // do something
    }

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateCustomer(Customer customer) {
        // do something
    }
}

A Transaction represents a unit of work with a database.

In spring TransactionDefinition interface that defines Spring-compliant transaction properties. @Transactional annotation describes transaction attributes on a method or class.

@Autowired
private TestDAO testDAO;

@Transactional(propagation=TransactionDefinition.PROPAGATION_REQUIRED,isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
public void someTransactionalMethod(User user) {

  // Interact with testDAO

}

Propagation (Reproduction) : is uses for inter transaction relation. (analogous to java inter thread communication)

+-------+---------------------------+------------------------------------------------------------------------------------------------------+
| value |        Propagation        |                                             Description                                              |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
|    -1 | TIMEOUT_DEFAULT           | Use the default timeout of the underlying transaction system, or none if timeouts are not supported. |
|     0 | PROPAGATION_REQUIRED      | Support a current transaction; create a new one if none exists.                                      |
|     1 | PROPAGATION_SUPPORTS      | Support a current transaction; execute non-transactionally if none exists.                           |
|     2 | PROPAGATION_MANDATORY     | Support a current transaction; throw an exception if no current transaction exists.                  |
|     3 | PROPAGATION_REQUIRES_NEW  | Create a new transaction, suspending the current transaction if one exists.                          |
|     4 | PROPAGATION_NOT_SUPPORTED | Do not support a current transaction; rather always execute non-transactionally.                     |
|     5 | PROPAGATION_NEVER         | Do not support a current transaction; throw an exception if a current transaction exists.            |
|     6 | PROPAGATION_NESTED        | Execute within a nested transaction if a current transaction exists.                                 |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+

Isolation : Isolation is one of the ACID (Atomicity, Consistency, Isolation, Durability) properties of database transactions. Isolation determines how transaction integrity is visible to other users and systems. It uses for resource locking i.e. concurrency control, make sure that only one transaction can access the resource at a given point.

Locking perception: isolation level determines the duration that locks are held.

+---------------------------+-------------------+-------------+-------------+------------------------+
| Isolation Level Mode      |  Read             |   Insert    |   Update    |       Lock Scope       |
+---------------------------+-------------------+-------------+-------------+------------------------+
| READ_UNCOMMITTED          |  uncommitted data | Allowed     | Allowed     | No Lock                |
| READ_COMMITTED (Default)  |   committed data  | Allowed     | Allowed     | Lock on Committed data |
| REPEATABLE_READ           |   committed data  | Allowed     | Not Allowed | Lock on block of table |
| SERIALIZABLE              |   committed data  | Not Allowed | Not Allowed | Lock on full table     |
+---------------------------+-------------------+-------------+-------------+------------------------+

Read perception: the following 3 kinds of major problems occurs:

  • Dirty reads : reads uncommitted data from another tx(transaction).
  • Non-repeatable reads : reads committed UPDATES from another tx.
  • Phantom reads : reads committed INSERTS and/or DELETES from another tx

Isolation levels with different kinds of reads:

+---------------------------+----------------+----------------------+----------------+
| Isolation Level Mode      |  Dirty reads   | Non-repeatable reads | Phantoms reads |
+---------------------------+----------------+----------------------+----------------+
| READ_UNCOMMITTED          | allows         | allows               | allows         |
| READ_COMMITTED (Default)  | prevents       | allows               | allows         |
| REPEATABLE_READ           | prevents       | prevents             | allows         |
| SERIALIZABLE              | prevents       | prevents             | prevents       |
+---------------------------+----------------+----------------------+----------------+

for examples


PROPAGATION_REQUIRED = 0; If DataSourceTransactionObject T1 is already started for Method M1. If for another Method M2 Transaction object is required, no new Transaction object is created. Same object T1 is used for M2.

PROPAGATION_MANDATORY = 2; method must run within a transaction. If no existing transaction is in progress, an exception will be thrown.

PROPAGATION_REQUIRES_NEW = 3; If DataSourceTransactionObject T1 is already started for Method M1 and it is in progress (executing method M1). If another method M2 start executing then T1 is suspended for the duration of method M2 with new DataSourceTransactionObject T2 for M2. M2 run within its own transaction context.

PROPAGATION_NOT_SUPPORTED = 4; If DataSourceTransactionObject T1 is already started for Method M1. If another method M2 is run concurrently. Then M2 should not run within transaction context. T1 is suspended till M2 is finished.

PROPAGATION_NEVER = 5; None of the methods run in transaction context.


An isolation level: It is about how much a transaction may be impacted by the activities of other concurrent transactions. It a supports consistency leaving the data across many tables in a consistent state. It involves locking rows and/or tables in a database.

The problem with multiple transaction

Scenario 1. If T1 transaction reads data from table A1 that was written by another concurrent transaction T2. If on the way T2 is rollback, the data obtained by T1 is invalid one. E.g. a=2 is original data. If T1 read a=1 that was written by T2. If T2 rollback then a=1 will be rollback to a=2 in DB. But, now, T1 has a=1 but in DB table it is changed to a=2.

Scenario2. If T1 transaction reads data from table A1. If another concurrent transaction (T2) update data on table A1. Then the data that T1 has read is different from table A1. Because T2 has updated the data on table A1. E.g. if T1 read a=1 and T2 updated a=2. Then a!=b.

Scenario 3. If T1 transaction reads data from table A1 with certain number of rows. If another concurrent transaction (T2) inserts more rows on table A1. The number of rows read by T1 is different from rows on table A1.

Scenario 1 is called Dirty reads.

Scenario 2 is called Non-repeatable reads.

Scenario 3 is called Phantom reads.

So, isolation level is the extend to which Scenario 1, Scenario 2, Scenario 3 can be prevented. You can obtain complete isolation level by implementing locking. That is preventing concurrent reads and writes to the same data from occurring. But it affects performance. The level of isolation depends upon application to application how much isolation is required.

ISOLATION_READ_UNCOMMITTED: Allows to read changes that haven’t yet been committed. It suffer from Scenario 1, Scenario 2, Scenario 3.

ISOLATION_READ_COMMITTED: Allows reads from concurrent transactions that have been committed. It may suffer from Scenario 2 and Scenario 3. Because other transactions may be updating the data.

ISOLATION_REPEATABLE_READ: Multiple reads of the same field will yield the same results untill it is changed by itself. It may suffer from Scenario 3. Because other transactions may be inserting the data.

ISOLATION_SERIALIZABLE: Scenario 1, Scenario 2, Scenario 3 never happen. It is complete isolation. It involves full locking. It affects performace because of locking.

You can test using:

public class TransactionBehaviour {
   // set is either using xml Or annotation
    DataSourceTransactionManager manager=new DataSourceTransactionManager();
    SimpleTransactionStatus status=new SimpleTransactionStatus();
   ;
  
    
    public void beginTransaction()
    {
        DefaultTransactionDefinition Def = new DefaultTransactionDefinition();
        // overwrite default PROPAGATION_REQUIRED and ISOLATION_DEFAULT
        // set is either using xml Or annotation
        manager.setPropagationBehavior(XX);
        manager.setIsolationLevelName(XX);
       
        status = manager.getTransaction(Def);
    
    }

    public void commitTransaction()
    {
       
      
            if(status.isCompleted()){
                manager.commit(status);
        } 
    }

    public void rollbackTransaction()
    {
       
            if(!status.isCompleted()){
                manager.rollback(status);
        }
    }
    Main method{
        beginTransaction()
        M1();
        If error(){
            rollbackTransaction()
        }
         commitTransaction();
    }
   
}

You can debug and see the result with different values for isolation and propagation.


You almost never want to use Read Uncommited since it's not really ACID compliant. Read Commmited is a good default starting place. Repeatable Read is probably only needed in reporting, rollup or aggregation scenarios. Note that many DBs, postgres included don't actually support Repeatable Read, you have to use Serializable instead. Serializable is useful for things that you know have to happen completely independently of anything else; think of it like synchronized in Java. Serializable goes hand in hand with REQUIRES_NEW propagation.

I use REQUIRES for all functions that run UPDATE or DELETE queries as well as "service" level functions. For DAO level functions that only run SELECTs, I use SUPPORTS which will participate in a TX if one is already started (i.e. being called from a service function).


Isolation level defines how the changes made to some data repository by one transaction affect other simultaneous concurrent transactions, and also how and when that changed data becomes available to other transactions. When we define a transaction using the Spring framework we are also able to configure in which isolation level that same transaction will be executed.

@Transactional(isolation=Isolation.READ_COMMITTED)
public void someTransactionalMethod(Object obj) {

}

READ_UNCOMMITTED isolation level states that a transaction may read data that is still uncommitted by other transactions.

READ_COMMITTED isolation level states that a transaction can't read data that is not yet committed by other transactions.

REPEATABLE_READ isolation level states that if a transaction reads one record from the database multiple times the result of all those reading operations must always be the same.

SERIALIZABLE isolation level is the most restrictive of all isolation levels. Transactions are executed with locking at all levels (read, range and write locking) so they appear as if they were executed in a serialized way.

Propagation is the ability to decide how the business methods should be encapsulated in both logical or physical transactions.

Spring REQUIRED behavior means that the same transaction will be used if there is an already opened transaction in the current bean method execution context.

REQUIRES_NEW behavior means that a new physical transaction will always be created by the container.

The NESTED behavior makes nested Spring transactions to use the same physical transaction but sets savepoints between nested invocations so inner transactions may also rollback independently of outer transactions.

The MANDATORY behavior states that an existing opened transaction must already exist. If not an exception will be thrown by the container.

The NEVER behavior states that an existing opened transaction must not already exist. If a transaction exists an exception will be thrown by the container.

The NOT_SUPPORTED behavior will execute outside of the scope of any transaction. If an opened transaction already exists it will be paused.

The SUPPORTS behavior will execute in the scope of a transaction if an opened transaction already exists. If there isn't an already opened transaction the method will execute anyway but in a non-transactional way.


Transaction Isolation and Transaction Propagation although related but are clearly two very different concepts. In both cases defaults are customized at client boundary component either by using Declarative transaction management or Programmatic transaction management. Details of each isolation levels and propagation attributes can be found in reference links below.

Transaction Isolation

For given two or more running transactions/connections to a database, how and when are changes made by queries in one transaction impact/visible to the queries in a different transaction. It also related to what kind of database record locking will be used to isolate changes in this transaction from other transactions and vice versa. This is typically implemented by database/resource that is participating in transaction.

.

Transaction Propagation

In an enterprise application for any given request/processing there are many components that are involved to get the job done. Some of this components mark the boundaries (start/end) of a transaction that will be used in respective component and it's sub components. For this transactional boundary of components, Transaction Propogation specifies if respective component will or will not participate in transaction and what happens if calling component already has or does not have a transaction already created/started. This is same as Java EE Transaction Attributes. This is typically implemented by the client transaction/connection manager.

Reference:


I have run outerMethod,method_1 and method_2 with different propagation mode.

Below is the output for different propagation mode.

  • Outer Method

    @Transactional
    @Override
    public void outerMethod() {
        customerProfileDAO.method_1();
        iWorkflowDetailDao.method_2();
    }
    
  • Method_1

    @Transactional(propagation=Propagation.MANDATORY)
    public void method_1() {
        Session session = null;
        try {
            session = getSession();
            Temp entity = new Temp(0l, "XXX");
            session.save(entity);
            System.out.println("Method - 1 Id "+entity.getId());
        } finally {
            if (session != null && session.isOpen()) {
            }
        }
    }
    
  • Method_2

    @Transactional()
    @Override
    public void method_2() {
        Session session = null;
        try {
            session = getSession();
            Temp entity = new Temp(0l, "CCC");
            session.save(entity);
            int i = 1/0;
            System.out.println("Method - 2 Id "+entity.getId());
        } finally {
            if (session != null && session.isOpen()) {
            }
        }
    }
    
      • outerMethod - Without transaction
      • method_1 - Propagation.MANDATORY) -
      • method_2 - Transaction annotation only
      • Output: method_1 will throw exception that no existing transaction
      • outerMethod - Without transaction
      • method_1 - Transaction annotation only
      • method_2 - Propagation.MANDATORY)
      • Output: method_2 will throw exception that no existing transaction
      • Output: method_1 will persist record in database.
      • outerMethod - With transaction
      • method_1 - Transaction annotation only
      • method_2 - Propagation.MANDATORY)
      • Output: method_2 will persist record in database.
      • Output: method_1 will persist record in database. -- Here Main Outer existing transaction used for both method 1 and 2
      • outerMethod - With transaction
      • method_1 - Propagation.MANDATORY) -
      • method_2 - Transaction annotation only and throws exception
      • Output: no record persist in database means rollback done.
      • outerMethod - With transaction
      • method_1 - Propagation.REQUIRES_NEW)
      • method_2 - Propagation.REQUIRES_NEW) and throws 1/0 exception
      • Output: method_2 will throws exception so method_2 record not persisted.
      • Output: method_1 will persist record in database.
      • Output: There is no rollback for method_1

You can use like this:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public EventMessage<ModificaOperativitaRapporto> activate(EventMessage<ModificaOperativitaRapporto> eventMessage) {
//here some transaction related code
}

You can use this thing also:

public interface TransactionStatus extends SavepointManager {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    void flush();
    boolean isCompleted();
}

Enough explanation about each parameter is given by other answers; However you asked for a real world example, here is the one that clarifies the purpose of different propagation options:

Suppose you're in charge of implementing a signup service in which a confirmation e-mail is sent to the user. You come up with two service objects, one for enrolling the user and one for sending e-mails, which the latter is called inside the first one. For example something like this:

/* Sign Up service */
@Service
@Transactional(Propagation=REQUIRED)
class SignUpService{
 ...
 void SignUp(User user){
    ...
    emailService.sendMail(User);
 }
}

/* E-Mail Service */
@Service
@Transactional(Propagation=REQUIRES_NEW)
class EmailService{
 ...
 void sendMail(User user){
  try{
     ... // Trying to send the e-mail
  }catch( Exception)
 }
}

You may have noticed that the second service is of propagation type REQUIRES_NEW and moreover chances are it throws an exception (SMTP server down ,invalid e-mail or other reasons).You probably don't want the whole process to roll-back, like removing the user information from database or other things; therefore you call the second service in a separate transaction.

Back to our example, this time you are concerned about the database security, so you define your DAO classes this way:

/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO{
 // some CRUD methods
}

Meaning that whenever a DAO object, and hence a potential access to db, is created, we need to reassure that the call was made from inside one of our services, implying that a live transaction should exist; otherwise an exception occurs.Therefore the propagation is of type MANDATORY.


Examples related to java

Under what circumstances can I call findViewById with an Options Menu / Action Bar item? How much should a function trust another function How to implement a simple scenario the OO way Two constructors How do I get some variable from another class in Java? this in equals method How to split a string in two and store it in a field How to do perspective fixing? String index out of range: 4 My eclipse won't open, i download the bundle pack it keeps saying error log

Examples related to spring

Are all Spring Framework Java Configuration injection examples buggy? Two Page Login with Spring Security 3.2.x Access blocked by CORS policy: Response to preflight request doesn't pass access control check Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean Failed to auto-configure a DataSource: 'spring.datasource.url' is not specified Spring Data JPA findOne() change to Optional how to use this? After Spring Boot 2.0 migration: jdbcUrl is required with driverClassName The type WebMvcConfigurerAdapter is deprecated No converter found capable of converting from type to type

Examples related to transactional

Spring @Transactional - isolation, propagation Spring - @Transactional - What happens in background?

Examples related to isolation

Spring @Transactional - isolation, propagation

Examples related to propagation

Spring @Transactional - isolation, propagation