Spring @Transactional - isolation, propagation
Asked Answered
V

10

553

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

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

Valueless answered 13/12, 2011 at 14:28 Comment(0)
O
543

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.

The default value for @Transactional is REQUIRED, and this is often what you want.

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.

Defaults may vary between difference databases. As an example, for MariaDB it is REPEATABLE READ.


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 its own sub-transaction.

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

Odetteodeum answered 13/12, 2011 at 14:28 Comment(4)
How does that last link relate to what you're talking about? According to the linked docs, it's the session that seems to state what the current transaction is, not the session factory.Matriarchate
@Donal, oh sorry that wasn't clear. My point was, since sessionFactory.getCurrentTransaction() was added there is no need to run HibernateTemplate anymore to manage transactions. I removed it :)Jacintajacinth
My question was just about where the link was pointing, really. :-)Matriarchate
how to get the changes made in current transaction-#36133167Transversal
H
394

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.

Harbard answered 22/4, 2013 at 16:10 Comment(5)
how to get the changes made in current transaction-#36133167Transversal
What is the interaction between isolation level and propagation? If method 1 starts a transaction with isolation level, say, READ_COMMITTED, and later calls method2 with level REPEATABLE_READ, surely method 2 has to be executed in its own , new transaction, regardless of what propagation behaviour it specifies (e.g. only REQUIRED)?Seiter
This is really late to the show, but when PROPAGATION_REQUIRES_NEW, what happens in to T1 (which is used by M1) if another new call happened to M1? (say M1.1)Poster
@CornelMasson I believe I have a very similar question as yours in mind. I created a specific SO question for it.Crenulation
There is another problem with concurrent transactions: I call it Scenario 0: Lost update which the Read uncommitted avoids.Siple
G
138

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

Gripe answered 23/4, 2013 at 10:6 Comment(3)
Perfect example for REQUIRES_NEW.Quire
Good explanation! By the way what is the default for propagation? Also would be even better if you could give an example like this for isolation as well. Thanks a lot.Outgoing
@PrakashK Default is REQUIRED. (docs.spring.io/spring-framework/docs/current/javadoc-api/org/…)Rist
P
68

Following definitions are from byteslounge.com tutorials 1 and 2:

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.

Pugnacious answered 4/12, 2015 at 3:53 Comment(3)
If you could add when to use which one, would be much more beneficial.Tumblebug
Give some examples, It would be very helpful for beginnersCountdown
Please clear this doubt, is the isolation level only concerned with db operations or all operations happening inside the service layer ? If it is connected to all operations in service layer, what does read_uncommitted mean ?Sweetie
W
43

A Transaction represents a unit of work with a database.

Transaction propagation: Spring allows you to specify how transactions should propagate across method calls and nested transactions.

Transaction isolation: Spring allows you to specify the isolation level of your transactions, so you can ensure that they are isolated from other concurrent transactions.

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 (Low)    |  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 (High)       |   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

Below is a chart which shows which transaction isolation level solves which problems of concurrency:

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

for examples

Wampum answered 6/4, 2018 at 3:12 Comment(1)
Great Post, Propagation: NOT_SUPPORTED : Execute non-transactionally, suspend the current transaction if one exists. Spring Doc: docs.spring.io/spring-framework/docs/current/javadoc-api/org/….Angulate
B
26

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

Boito answered 13/12, 2011 at 15:13 Comment(0)
D
16

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:

Dame answered 26/4, 2013 at 18:11 Comment(1)
Great, All info at one place, Links are very helpful, Thank you @Gladwin BurbozCountdown
E
10

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

Below is the output for different propagation modes.

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
Edgar answered 26/8, 2015 at 10:4 Comment(0)
M
2

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
    }
}
Muco answered 4/12, 2015 at 5:29 Comment(0)
M
0

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();
}
Muco answered 4/12, 2015 at 5:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.