|
| 1 | +--- |
| 2 | +title: Optimistic Offline Lock |
| 3 | +category: Concurrency |
| 4 | +language: en |
| 5 | +tag: |
| 6 | +- Data access |
| 7 | +--- |
| 8 | + |
| 9 | +## Intent |
| 10 | + |
| 11 | +Provide an ability to avoid concurrent changes of one record in relational databases. |
| 12 | + |
| 13 | +## Explanation |
| 14 | + |
| 15 | +Each transaction during object modifying checks equation of object's version before start of transaction |
| 16 | +and before commit itself. |
| 17 | + |
| 18 | +**Real world example** |
| 19 | +> Since people love money, the best (and most common) example is banking system: |
| 20 | +> imagine you have 100$ on your e-wallet and two people are trying to send you 50$ both at a time. |
| 21 | +> Without locking, your system will start **two different thread**, each of whose will read your current balance |
| 22 | +> and just add 50$. The last thread won't re-read balance and will just rewrite it. |
| 23 | +> So, instead 200$ you will have only 150$. |
| 24 | +
|
| 25 | +**In plain words** |
| 26 | +> Each transaction during object modifying will save object's last version and check it before saving. |
| 27 | +> If it differs, the transaction will be rolled back. |
| 28 | +
|
| 29 | +**Wikipedia says** |
| 30 | +> Optimistic concurrency control (OCC), also known as optimistic locking, |
| 31 | +> is a concurrency control method applied to transactional systems such as |
| 32 | +> relational database management systems and software transactional memory. |
| 33 | +
|
| 34 | +**Programmatic Example** |
| 35 | +Let's simulate the case from *real world example*. Imagine we have next entity: |
| 36 | + |
| 37 | +```java |
| 38 | +import lombok.AllArgsConstructor; |
| 39 | +import lombok.Data; |
| 40 | +import lombok.NoArgsConstructor; |
| 41 | + |
| 42 | +/** |
| 43 | + * Bank card entity. |
| 44 | + */ |
| 45 | +@Data |
| 46 | +@NoArgsConstructor |
| 47 | +@AllArgsConstructor |
| 48 | +public class Card { |
| 49 | + |
| 50 | + /** |
| 51 | + * Primary key. |
| 52 | + */ |
| 53 | + private long id; |
| 54 | + |
| 55 | + /** |
| 56 | + * Foreign key points to card's owner. |
| 57 | + */ |
| 58 | + private long personId; |
| 59 | + |
| 60 | + /** |
| 61 | + * Sum of money. |
| 62 | + */ |
| 63 | + private float sum; |
| 64 | + |
| 65 | + /** |
| 66 | + * Current version of object; |
| 67 | + */ |
| 68 | + private int version; |
| 69 | +} |
| 70 | +``` |
| 71 | + |
| 72 | +Then the correct modifying will be like this: |
| 73 | + |
| 74 | +```java |
| 75 | + |
| 76 | +import lombok.RequiredArgsConstructor; |
| 77 | + |
| 78 | +/** |
| 79 | + * Service to update {@link Card} entity. |
| 80 | + */ |
| 81 | +@RequiredArgsConstructor |
| 82 | +public class CardUpdateService implements UpdateService<Card> { |
| 83 | + |
| 84 | + private final JpaRepository<Card> cardJpaRepository; |
| 85 | + |
| 86 | + @Override |
| 87 | + @Transactional(rollbackFor = ApplicationException.class) //will roll back transaction in case ApplicationException |
| 88 | + public Card doUpdate(Card card, long cardId) { |
| 89 | + float additionalSum = card.getSum(); |
| 90 | + Card cardToUpdate = cardJpaRepository.findById(cardId); |
| 91 | + int initialVersion = cardToUpdate.getVersion(); |
| 92 | + float resultSum = cardToUpdate.getSum() + additionalSum; |
| 93 | + cardToUpdate.setSum(resultSum); |
| 94 | + //Maybe more complex business-logic e.g. HTTP-requests and so on |
| 95 | + |
| 96 | + if (initialVersion != cardJpaRepository.getEntityVersionById(cardId)) { |
| 97 | + String exMessage = String.format("Entity with id %s were updated in another transaction", cardId); |
| 98 | + throw new ApplicationException(exMessage); |
| 99 | + } |
| 100 | + |
| 101 | + cardJpaRepository.update(cardToUpdate); |
| 102 | + return cardToUpdate; |
| 103 | + } |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +## Applicability |
| 108 | + |
| 109 | +Since optimistic locking can cause degradation of system's efficiency and reliability due to |
| 110 | +many retries/rollbacks, it's important to use it safely. They are useful in case when transactions are not so long |
| 111 | +and does not distributed among many microservices, when you need to reduce network/database overhead. |
| 112 | + |
| 113 | +Important to note that you should not choose this approach in case when modifying one object |
| 114 | +in different threads is common situation. |
| 115 | + |
| 116 | +## Tutorials |
| 117 | + |
| 118 | +- [Offline Concurrency Control](https://www.baeldung.com/cs/offline-concurrency-control) |
| 119 | +- [Optimistic lock in JPA](https://www.baeldung.com/jpa-optimistic-locking) |
| 120 | + |
| 121 | +## Known uses |
| 122 | + |
| 123 | +- [Hibernate ORM](https://docs.jboss.org/hibernate/orm/4.3/devguide/en-US/html/ch05.html) |
| 124 | + |
| 125 | +## Consequences |
| 126 | + |
| 127 | +**Advantages**: |
| 128 | + |
| 129 | +- Reduces network/database overhead |
| 130 | +- Let to avoid database deadlock |
| 131 | +- Improve the performance and scalability of the application |
| 132 | + |
| 133 | +**Disadvantages**: |
| 134 | + |
| 135 | +- Increases complexity of the application |
| 136 | +- Requires mechanism of versioning |
| 137 | +- Requires rollback/retry mechanisms |
| 138 | + |
| 139 | +## Related patterns |
| 140 | + |
| 141 | +- [Pessimistic Offline Lock](https://martinfowler.com/eaaCatalog/pessimisticOfflineLock.html) |
| 142 | + |
| 143 | +## Credits |
| 144 | + |
| 145 | +- [Source (Martin Fowler)](https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html) |
| 146 | +- [Advantages and disadvantages](https://www.linkedin.com/advice/0/what-benefits-drawbacks-using-optimistic) |
| 147 | +- [Comparison of optimistic and pessimistic locks](https://www.linkedin.com/advice/0/what-advantages-disadvantages-using-optimistic) |
0 commit comments