Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 945
MatBanik Avatar asked Dec 13 '11 14:12

MatBanik


People also ask

What is @transactional propagation propagation Requires_new?

The main difference between them is if a method in Spring Business Activity/DAO class is annotated with Propagation. REQUIRES_NEW, then when the execution comes to this method, it will create a new transaction irrespective of the existing transaction whereas if the method is annotated with Propagation.

What is Spring transaction propagation?

Spring allows you to control the behavior of logical and physical transactions via transaction propagation mechanisms. There are seven types of transaction propagation mechanisms that you can set in a Spring application via org. springframework. transaction. annotation.

Which transaction propagation level always spawns a new transaction?

REQUIRES_NEW : Code will always run in a new transaction.


1 Answers

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 it's own sub-transaction.

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

like image 149
18 revs, 8 users 77% Avatar answered Oct 01 '22 10:10

18 revs, 8 users 77%