These anomalies can be avoided by using transaction isolation levels. In the JavaEE and Spring Framework worlds, there are four transaction isolation levels:
If we use READ_UNCOMMITTED, the transaction can read uncommitted data. This is where dirty reads will happen. If we use this read uncommitted level, Dirty Reads, Non-repeatable Reads, and Phantom Reads can occur. All of the anomalies will occur if we use the first level.
The second one is READ_COMMITTED. Ensures that our transactions read only the committed data. So if we use this level, then we will avoid Dirty Reads. Still, will have the Non-Repeatable reads problem. Dirty Reads are prevented. Non-repeatable Reads and Phantom Reads can still occur.
Then comes the REPEATABLE_READ, this is the popular one and most used one by default. Many databases have this as the default Transaction Repeatable Read. This is where we can prevent the Non-repeatable Read problem as well. But the Phantom Reads can still occur.
Finally, we have SERIALIZABLE which is the most strict transaction isolation level and also the less performing. As the transaction isolation level increases, as we switch from Read Uncommitted to Read Committed and then to Repeatable Read and Serializable, the performance of our application will go down because as we move closer to Serializable. If we use SERIALIZABLE, that means it’s almost like no two transactions can access the same set of data. At the same time, it could be a table-level lock. All the anomalies will be avoided.