patternjavaCritical
Spring @Transactional - isolation, propagation
Viewed 0 times
transactionalspringpropagationisolation
Problem
Can someone explain the isolation & propagation parameters in the
Basically when and why should I choose to change their default values?
@Transactional annotation via a real-world example?Basically when and why should I choose to change their default values?
Solution
Good question, although not a trivial one to answer.
Propagation
Defines how transactions relate to each other. Common options:
The default value for
Isolation
Defines the data contract between transactions.
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
Example of when a dirty read can occur:
So a sane default (if such can be claimed) could be
A practical example of where a new transaction will always be created when entering the
Had we instead used
Note also that the result of a
We can easily verify the behaviour with a test and see how results differ with propagation levels:
With a propagation level of
-
-
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.Code Snippets
thread 1 thread 2
| |
write(x) |
| |
| read(x)
| |
rollback |
v v
value (x) is now dirty (incorrect)public class FooService {
private Repository repo1;
private Repository repo2;
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void provideService() {
repo1.retrieveFoo();
repo2.retrieveFoo();
}
}@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 ...
}Context
Stack Overflow Q#8490852, score: 554
Revisions (0)
No revisions yet.