█████████ █████ ████ █████ ████
███░░░░░███ ░░███ ███░ ░░███ ░░███
░███ ░███ ████████ ████████ ██████ ░███ ███ ██████ ██████ ░███████ ░███ ██████ ████████
░███████████ ░░███░░███░░███░░███ ███░░███ ░███████ ███░░███ ███░░███ ░███░░███ ░███ ███░░███░░███░░███
░███░░░░░███ ░███ ░░░ ░███ ░███ ░███ ░███ ░███░░███ ░███ ░███░███████ ░███ ░███ ░███ ░███████ ░███ ░░░
░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░░███ ░███ ░███░███░░░ ░███ ░███ ░███ ░███░░░ ░███
█████ █████ █████ ████ █████░░██████ █████ ░░████░░██████ ░░██████ ████ █████ █████░░██████ █████
░░░░░ ░░░░░ ░░░░░ ░░░░ ░░░░░ ░░░░░░ ░░░░░ ░░░░ ░░░░░░ ░░░░░░ ░░░░ ░░░░░ ░░░░░ ░░░░░░ ░░░░░
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.*##(((#(#(((#%######*,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,/&&&&%#/((%#(#(//((((#%%%####%&&#*,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,/%%#%%((#%(%#(/#%#%&@@@@@@@@@@@&&&&&#,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,(###%%&#(&#%%%%&%%&&@@@%#(%%####((####%&&&#,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,*%%##%%&##(%%%%&&&%#&@&(#(%#%%%&&@@&&&%#%%%&&@&(,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,*#%&%#%%####(%#%#%###%###%%%#%%%%%%&@@@@@&@&&&&&&*,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,**#&&%%#(///((/((((/((//////(//((##%&&@@@@@@@&&&&/,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,*/&&%#(//**,,,,,,,...,.......,,,,,,*(#%&&@@@@&&/,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,*%%%#(/***,,,............ ........,,,//#%@@@@&%*,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,%%%#(/**,,,,,........... .......,***#&@@@&#,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,#%%(/**,,,,,,,.....................,,**#%%&&&*,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,#%#(***,,,,,.......................,,*/#&&&&%*,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,*%(/**,,,,,,.....................,,,,*/%&&&&#,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,*/#(/*****,,,,..,...................,**/#&&&%/,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,*(##/(##((/(###((/**,,,,***////*/****,*/*(%%%/*,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,/(/((///#///#((/(((,...*//(//%(##%#/////*/##/***,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,//((**,,,*******,**,...,,,*,*,*,.,,,,,,**/((,,,*,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,//#/*,,,......,,***,...,,,............,,*/(//,.*,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,////*,,......,,***,......,.............,*/#/,*,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,////*,,.,,,*///**,.......,,,,,,......,,*/##*,,,*,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,*((/***/(((((#%%(/***/((//,,,*(*,..,,*/(##,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,*#(/*/((///((((##(((((#((///*//(*,,*//##(/*,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,(#(//##(#&&#((/////////(((#((((/**/((%%*,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,*/#%####/**/(.,..., ..,.,/(**((/*/((#%%#,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,*(&%%%#(/***,,*/(///*,,,,,,///((###%%%/,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,**%&&%#(/**,,,***,,,,,,,,,*/((#%%%%%%%/,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,*(&&%%#((/*********,,,,**/(#%%%%%%%/.%#,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,*%&%%##(///****/****//(####%&%&%%/..(&&(,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,*((%&%%%%(((((/////(/((##%%%%&%%(,. .%&&&&*,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,/#%&&&@&(,/#%%&%###(((#(####%%%%%%%%#,.. .#&&&&&&#*,,,,,,,,,
,,,,,,,,,*/(%&&&&&&&&&&&&&%.,*#((#%%%%#%%%#%%%%%%%%#%*. . .. #&&&&&&&&&&&/,,,,,,
,,*(%&&&&&&&&&&&&&&&&&&&&&..,,(#((((###%%%%%%%%###/. .. *%&&&&&&&&&&&&&&&#,, ,
███████████ █████ ███
░█░░░███░░░█ ░░███ ░░░
░ ░███ ░ ████████ ██████ ████████ █████ ██████ ██████ ███████ ████ ██████ ████████ █████
░███ ░░███░░███ ░░░░░███ ░░███░░███ ███░░ ░░░░░███ ███░░███░░░███░ ░░███ ███░░███░░███░░███ ███░░
░███ ░███ ░░░ ███████ ░███ ░███ ░░█████ ███████ ░███ ░░░ ░███ ░███ ░███ ░███ ░███ ░███ ░░█████
░███ ░███ ███░░███ ░███ ░███ ░░░░███ ███░░███ ░███ ███ ░███ ███ ░███ ░███ ░███ ░███ ░███ ░░░░███
█████ █████ ░░████████ ████ █████ ██████ ░░████████░░██████ ░░█████ █████░░██████ ████ █████ ██████
░░░░░ ░░░░░ ░░░░░░░░ ░░░░ ░░░░░ ░░░░░░ ░░░░░░░░ ░░░░░░ ░░░░░ ░░░░░ ░░░░░░ ░░░░ ░░░░░ ░░░░░░
- A transaction is basically all the interactions with a database that together is considered as one unit of work.
- So considering a back account, subtracting money from one account and adding it to another should be one transaction.
- Now in fact that would work with systems that are eventually consistent;
- eventing systems registering all the between states as well enabling logging on events etc.
Database transactions are ACID:
- Atomic
- All or nothing
- Consistent
- No constraints violated
- Isolation
- Users don't affect each other
- Durable
- Once data is committed, it is permanent
A real DBMS system supports all four is said to be ACID compliant; sorry NoSQL databases.
To be fare those are BASE; Basically Available, Soft state, Eventually consistent. This means that in between ordering and fulfilling something could happen that would result in a rollback of your purchase. The reason is that transactions take up a small time, while NoSql databases are designed for ultra fast write actions.
- Begin the transaction using begin transaction command.
- Perform various deleted, update or insert operations using SQL queries.
- If all the operation are successful then perform commit otherwise rollback all the operations.
In Spring we use an abstraction to work with transactions, knowing the PlatformTransactionManager for imperative development or ReactiveTransactionManager if you want to go reactive.
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition);
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
The following interface exists only from version 5.2 of Spring and up.
public interface ReactiveTransactionManager extends TransactionManager {
Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;
Mono<Void> commit(ReactiveTransaction status) throws TransactionException;
Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
}
So transactions are all about this COMMIT and ROLLBACK scenario. When NO errors are thrown, a transaction is COMMITTED to the database.
So how most databases handle this, is that they maintain a ROLLBACK table, preserving the before state until the COMMIT is done. After the COMMIT the ROLLBACK information will be removed from the table.
As you see in the interface there is a TransactionDefinition when getting a transaction, this is set to configure the properties of the transaction.
A Transaction can have 4 types of properties:
- Propagation
- Isolation
- Timeout
- Read-only
So as said Transactions are about the boundaries of your transaction, where does the unit of work start and where does it end?
If you want transactions in Spring you have to tell Spring to enable it, with the annotation @EnableTransactionManagement. But due to AutoConfiguration we hardly ever do that in our production code, before without Spring Boot we did and it looks like the snippet below.
@Configuration
@EnableTransactionManagement
public class PersistenceJPAConfig{
@Bean
public LocalContainerEntityManagerFactoryBean
entityManagerFactoryBean(){
//...
}
@Bean
public PlatformTransactionManager transactionManager(){
JpaTransactionManager transactionManager
= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
entityManagerFactoryBean().getObject() );
return transactionManager;
}
}
LocalContainerEntityManagerFactoryBean - container managed LocalEntityManagerFactoryBean - application managed
A Big Note : For spring based applications, the difference is not much. Spring only plays roles ( as container if you configure LocalContainerEntityManagerFactoryBean and as application if you configure LocalEntityManagerFactoryBean)
Declaring that something is a unit of work we annotate it with @Transactional in Spring with JPA, Hibernate or any other implementation. You can do that both on a service level or on a method level.
@Service
@Transactional
public class FooService {
//...
}
@Service
@Transactional
public class FooService {
//...
}
Now the way this works within Spring is by the use of Proxy classes. NOTE: that when you have a class calling an inner method, no new transaction will be started because of this.
So repeat after me: Any self-invocation calls will not start any transaction
Another point of attention with these proxies is that only public methods should be annotated with @Transactional. Methods of any other visibilities will simply ignore the annotation silently as these are not proxied.
Also be aware that if you annotate Interfaces, transactions may not be started because annotations are not inherited.
Now when an error is thrown, the entire persistence unit is rolled back. Note that when you don't set up a transaction (by annotating it with @Transactional) the data is COMMITTED even though an error might have been thrown.
In this Demo we use JPA with a Product and a ProductRepository.
I have made 2 services one without transactions and one with. To see this in action see: TransactionalProductServiceTest vs ProductServiceTest
Propagation is about how multiple annotated pieces of code group together. By default Spring is configured with PROPAGATION_REQUIRED that means: support a current transaction; create a new one if none exists.
In a default situation this is all you need, Spring wraps every method that is called via the proxy class in one big transaction, starting from the first point it is annotated with @Transactional.
Note that if you do not annotate anything your repository is the where the transaction starts, so each repository call is treated like an individual unit of work. What could lead to inconsistency in the end.
So there are other behaviours to use, but in real live code bases this leads more often to issues than wanted behaviour.
On of the best known other modes is REQUIRES_NEW. Now REQUIRES_NEW might have a certain use case; but let's see how it works first.
So basically with REQUIRES_NEW you tell the system, that when wrapped in an outer transaction, this inner transaction has to be treated like independent from what happens in the outside world. When an exception is thrown in the inner method, only that part of the data needs to be reverted. Or if in the outer method an exception is thrown, only that data needs to be reverted.
PROPAGATION_REQUIRES_NEW starts a new, independent "inner" transaction for the given scope. This transaction will be committed or rolled back completely independent from the outer transaction, having its own isolation scope, its own set of locks, etc. The outer transaction will get suspended at the beginning of the inner one, and resumed once the inner one has completed.
See: RequiresNewOuterTransactionServiceTest
PROPAGATION_NESTED on the other hand starts a "nested" transaction, which is a true subtransaction of the existing one. What will happen is that a savepoint will be taken at the start of the nested transaction. Íf the nested transaction fails, it will roll back to that savepoint. The nested transaction is part of of the outer transaction, so it will only be committed at the end of of the outer transaction.
What is important is that this propagation mode is NOT supported by JPA.
See: NestedOuterTransactionServiceTest
- PROPAGATION_NEVER will brake if executed from a current running transaction.
- NOT_SUPPORTED will suspend the current transaction and create a new one
- PROPAGATION_SUPPORTS will respond to the current transaction, or create a new one.
- PROPAGATION_MANDATORY can not be called without a current running transaction
For me there is rarely a use case where you want any of these. The default behaviour of Spring wrapping all it comes across should be mostly sufficient in a normal project. But if you do need them, then be very aware of the code that might call your code in the future; because this is one of the most error prone parts of transactions.
Isolation you need to know about common database errors and how they are solved:
Transaction 1 | Transaction 2 |
---|---|
Select * From DATA where (gives 20) | |
UPDATE DATA SET x + 1 WHERE z | |
Select * From DATA where (gives 21) |
Now if Transaction 2 roles back Transaction 1 contains a dirty read
Transaction 1 | Transaction 2 |
---|---|
Select * From DATA where (gives 20) | |
UPDATE DATA SET x + 1 WHERE z | |
COMMIT; | |
Select * From DATA where (gives 21) |
Now Transaction 1 contains a non repeatable read
Transaction 1 | Transaction 2 |
---|---|
Select * From DATA where (gives 200 rows) Limit 10 | |
INSERT 20 ROWS | |
COMMIT; | |
Select * From DATA where (gives 210 rows) Limit 20 | |
result set contains overlap and not all records |
Databases can use table or row locks to prevent these kind of issues, as you can understand the higher the locking level - the slower the performance
- Read uncommitted
- permits dirty reads, non repeatable reads and phantom reads.
- Read committed
- permits non repeatable reads and phantom reads.
- Repeatable read
- permits only phantom reads.
- Serializable
- does not permit any read errors.
To see serializable in action: https://www.youtube.com/watch?v=NHKHzwolbKU
OH OH!
https://www.baeldung.com/jpa-pessimistic-locking
- POSTGRES default = Read committed
- ORACLE Read commited OR serializble (NO OTHER SUPPORT)
So it doesn't feel like we should really deep dive into the others besides READ COMMITTED (since stronger modes only make your systems slower)
(change my mind ...)
Configure logging of “org.springframework.transaction” to be configured with a logging level of TRACE. This way you can see more information about what is going on in your transactions.
Oh yes! Our live issue, flood of optimistic locking exceptions.
So what happened is we have a call on our systems that does to much. When you just want to get the data, it also checks if it needs to refresh; therefor creating a lot of interaction with the data it is manipulating itself.
Now there is an optimistic locking exception being thrown, and this is good because this is actually saying:
Hi there, how nice of you
Spring / Hibernate @Version
@History ?!