I have spent my morning reading all the top articles that Google churns up on optimistic locking, and for the life of me, I still don't really get it.
I understand that optimistic locking involves the addition of a column for tracking the record's "version", and that this column can be a timestamp, a counter, or any other version-tracking construct. But I'm still not understanding how that ensures WRITE integrity (meaning that if multiple process are updating the same entity at the same time, that afterwards, the entity correctly reflects the true state it should be in).
Can someone provide a concrete, easy-to-understand example of how optimistic locking could be used in Java (against, perhaps, a MySQL DB). Let's say we have a Person
entity:
public class Person {
private String firstName;
private String lastName;
private int age;
private Color favoriteColor;
}
And that Person
instances get persisted to a people
MySQL table:
CREATE TABLE people (
person_id PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL, # } I realize these column defs are not valid but this is just pseudo-code
age INT NOT NULL,
color_id FOREIGN KEY (colors) NOT NULL # Say we also have a colors table and people has a 1:1 relationship with it
);
Now let's say there are 2 software systems, or 1 system with 2 threads on it, that are trying to update the same Person
entity at the same time:
people
and/or colors
tables? (Looking for specific DDL example)people
/colors
tables correctly? Basically, I'm looking to see optimistic locking in action, with an easy-to-follow explanation of why it works.In order to use optimistic locking, we need to have an entity including a property with @Version annotation. While using it, each transaction that reads data holds the value of the version property. Before the transaction wants to make an update, it checks the version property again.
Solution. To resolve this error we have two ways: Get the latest object from the database and set the old object values if you need those values to be persisted to the new object and merge it. For the old object set the latest version from Database.
Optimistic locking , where a record is locked only when changes are committed to the database. Pessimistic locking , where a record is locked while it is edited.
public class OptimisticLockException extends PersistenceException. Thrown by the persistence provider when an optimistic locking conflict occurs. This exception may be thrown as part of an API call, a flush or at commit time. The current transaction, if one is active, will be marked for rollback.
Normally when you look into optimistic locking you also use a library like Hibernate or an other JPA-Implementation with @Version
support.
Example could read like this:
public class Person { private String firstName; private String lastName; private int age; private Color favoriteColor; @Version private Long version; }
while obviously there is no point of adding a @Version
annotation if you are not using a framework which supports this.
The DDL could then be
CREATE TABLE people ( person_id PRIMARY KEY AUTO_INCREMENT, first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, # } I realize these column defs are not valid but this is just pseudo-code age INT NOT NULL, color_id FOREIGN KEY (colors) NOT NULL, # Say we also have a colors table and people has a 1:1 relationship with it version BIGINT NOT NULL );
To get both steps done without risking an other process changing data between both steps it is normally handled through a statement like
UPDATE Person SET lastName = 'married', version=2 WHERE person_id = 42 AND version = 1;
After executing the statement you check if you updated a row or not. If you did, nobody else changed the data since you've read it, otherwise somebody else changed the data. If somebody else changed the data you will normally receive an OptimisticLockException
by the library you are using.
This exception should cause all changes to be revoked and the process of changing the value to be restarted as the condition upon which the entity was to be updated may no longer be applicable.
So no collision:
Collision:
If Colour is another object you should put a version there by the same scheme.
OptimisticLockException
sIf many different applications access your data you may be best off using a column automatically updated by the database. e.g. for MySQL
version TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
this way the applications implementing optimistic locking will notice changes by dumb applications.
If you update entities more often than the resolution of TIMESTAMP
or the Java-interpretation of it, ths approach can fail to detect certain changes. Also if you let Java generate the new TIMESTAMP
you need to ensure that all machines running your applications are in perfect time-sync.
If all of your applications can be altered an integer, long, ... version is normally a good solution as it will never suffer from differently set clocks ;-)
There are other scenarios. You could e.g. use a hash or even randomly generate a String
every time a row is to be changed. Important is, that you don't repeat values while any process is holding data for local processing or inside a cache as that process will not be able to detect change by looking at the version-column.
As a last resort you may use the value of all fields as version. While this will be the most expensive approach in most cases it is a way to get similar results without changing the table structure. If you use Hibernate there is the @OptimisticLocking
-annotation to enforce this behavior. Use @OptimisticLocking(type = OptimisticLockType.ALL)
on the entity-class to fail if any row changed since you have read the entity or @OptimisticLocking(type = OptimisticLockType.DIRTY)
to just fail when another process changed the fields you changed, too.
@TheConstructor: Great explanation of what it is and is not. When you said "Optimistic Locking is no magic to merge conflicting changes" I wanted to comment. I used to manage a DataFlex application which allowed users to edit records on a form. When they pushed the "Save" button, the app would do what was called a "multi-user re-read" of the data - pulling the current values - and compare to what the user had modified. If the fields the user modified had not been changed in the meantime, then just those fields would be written back to the record (which was locked only during the re-read + write operation) and therefore, 2 users could transparently modify different fields on the same record with no issues. It did not require version stamps, just a knowledge of which fields were modified.
Granted, this is not a perfect solution, but it did the job in that case. It was optimistic and it did allow unrelated changes and it gave the user an error on conflicting changes. This was the best that could be done, pre-SQL, but is still a good design principle today, perhaps for more object- or web-related scenarios.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With