35

I am confused about the working of LockModeTypes in JPA:

  1. LockModeType.Optimistic

    • it increments the version while committing.
    • Question here is : If I have version column in my entity and if I don't specify this lock mode then also it works similarly then what is the use of it?
  2. LockModeType.OPTIMISTIC_FORCE_INCREMENT

    • Here it increments the version column even though the entity is not updated.
    • but what is the use of it if any other process updated the same row before this transaction is committed? this transaction is anyways going to fail. so what is the use of this LockModeType.
  3. LockModeType.PESSIMISTIC_READ

    • This lock mode issues a select for update nowait(if no hint timeout specified)..
    • so basically this means that no other transaction can update this row until this transaction is committed, then its basically a write lock, why its named a Read lock?
  4. LockModeType.PESSIMISTIC_WRITE

    • This lock mode also issues a select for update nowait (if no hint timeout specified).
    • Question here is what is the difference between this lock mode and LockModeType.PESSIMISTIC_READ as I see both fires same queries?
  5. LockModeType.PESSIMISTIC_FORCE_INCREMENT

    • this does select for update nowait (if no hint timeout specified) and also increments the version number.
    • I totally didn't get the use of it.
    • why a version increment is required if for update no wait is there?
eatSleepCode
  • 4,427
  • 7
  • 44
  • 93

1 Answers1

61

I would first differentiate between optimistic and pessimistic locks, because they are different in their underlying mechanism.

Optimistic locking is fully controlled by JPA and only requires additional version column in DB tables. It is completely independent of underlying DB engine used to store relational data.

On the other hand, pessimistic locking uses locking mechanism provided by underlying database to lock existing records in tables. JPA needs to know how to trigger these locks and some databases do not support them or only partially.

Now to the list of lock types:

  1. LockModeType.Optimistic
    • If entities specify a version field, this is the default. For entities without a version column, using this type of lock isn't guaranteed to work on any JPA implementation. This mode is usually ignored as stated by ObjectDB. In my opinion it only exists so that you may compute lock mode dynamically and pass it further even if the lock would be OPTIMISTIC in the end. Not very probable usecase though, but it is always good API design to provide an option to reference even the default value.
  • Example:

       `LockModeType lockMode = resolveLockMode();
     A a = em.find(A.class, 1, lockMode);`
    
  1. LockModeType.OPTIMISTIC_FORCE_INCREMENT
  • This is a rarely used option. But it could be reasonable, if you want to lock referencing this entity by another entity. In other words you want to lock working with an entity even if it is not modified, but other entities may be modified in relation to this entity.
  • Example: We have entity Book and Shelf. It is possible to add Book to Shelf, but book does not have any reference to its shelf. It is reasonable to lock the action of moving a book to a shelf, so that a book does not end up in another shelf (due to another transaction) before end of this transaction. To lock this action, it is not sufficient to lock current book shelf entity, as the book does not have to be on a shelf yet. It also does not make sense to lock all target bookshelves, as they would be probably different in different transactions. The only thing that makes sense is to lock the book entity itself, even if in our case it does not get changed (it does not hold reference to its bookshelf).
  1. LockModeType.PESSIMISTIC_READ
  • this mode is similar to LockModeType.PESSIMISTIC_WRITE, but different in one thing: until write lock is in place on the same entity by some transaction, it should not block reading the entity. It also allows other transactions to lock using LockModeType.PESSIMISTIC_READ. The differences between WRITE and READ locks are well explained here (ObjectDB) and here (OpenJPA). If an entity is already locked by another transaction, any attempt to lock it will throw an exception. This behavior can be modified to waiting for some time for the lock to be released before throwing an exception and roll back the transaction. In order to do that, specify the javax.persistence.lock.timeout hint with the number of milliseconds to wait before throwing the exception. There are multiple ways to do this on multiple levels, as described in the Java EE tutorial.
  1. LockModeType.PESSIMISTIC_WRITE
  • this is a stronger version of LockModeType.PESSIMISTIC_READ. When WRITE lock is in place, JPA with the help of the database will prevent any other transaction to read the entity, not only to write as with READ lock.
  • The way how this is implemented in a JPA provider in cooperation with underlying DB is not prescribed. In your case with Oracle, I would say that Oracle does not provide something close to a READ lock. SELECT...FOR UPDATE is really rather a WRITE lock. It may be a bug in hibernate or just a decision that, instead of implementing custom "softer" READ lock, the "harder" WRITE lock is used instead. This mostly does not break consistency, but does not hold all rules with READ locks. You could run some simple tests with READ locks and long running transactions to find out if more transactions are able to acquire READ locks on the same entity. This should be possible, whereas not with WRITE locks.
  1. LockModeType.PESSIMISTIC_FORCE_INCREMENT
  • this is another rarely used lock mode. However, it is an option where you need to combine PESSIMISTIC and OPTIMISTIC mechanisms. Using plain PESSIMISTIC_WRITE would fail in following scenario:
    1. transaction A uses optimistic locking and reads entity E
    2. transaction B acquires WRITE lock on entity E
    3. transaction B commits and releases lock of E
    4. transaction A updates E and commits
  • in step 4, if version column is not incremented by transaction B, nothing prevents A from overwriting changes of B. Lock mode LockModeType.PESSIMISTIC_FORCE_INCREMENT will force transaction B to update version number and causing transaction A to fail with OptimisticLockException, even though B was using pessimistic locking.
  1. LockModeType.NONE
  • this is the default if entities don't provide a version field. It means that no locking is enabled conflicts will be resolved on best effort basis and will not be detected. This is the only lock mode allowed outside of a transaction
Adrian
  • 2,984
  • 15
  • 27
OndroMih
  • 7,280
  • 1
  • 26
  • 44
  • 1
    I also found an older article, that mentions that "At the time of writing, most of the persistence providers such as EclipseLink and Hibernate provided support for PESSIMISTIC_READ through PESSIMISTIC_WRITE." (http://www.developer.com/java/ent/article.php/3897331/Pessimistic-Locking-in-JPA-2-and-Hibernate.htm) – OndroMih Oct 12 '15 at 12:44
  • Here you go, I was really curious about why sometimes pessimistic READ behaves like WRITE, and I found out this written directly in JPA specification: "It is permissible for an implementation to use LockModeType.PESSIMISTIC_WRITE where LockModeType.PESSIMISTIC_READ was requested, but not vice versa." – OndroMih Oct 12 '15 at 12:52
  • in point 2: "so that a book does not end up in 2 shelves". If that is the case, wouldn't one use a @OneToMany relation in a shelf (ie one shelf can have many books) to represent this constraint? if so, adding the same book to a second shelf would fail regardless of the lock mode, as constraint violation on id uniqueness in the book_shelf table, isn't it? – arcuri82 Aug 27 '16 at 20:22
  • @arcuri82, you are right - obviously, with `@OneToMany` relation, a book cannot end up in 2 shelves. But it still may end up in another shelf by a parallel transaction, which we would overwrite in our transaction if we didn't use OPTIMISTIC_FORCE_INCREMENT. When we use it, an OptimisticLockException is correctly thrown and rollback follows. I fixed my example, thanks for your feedback. – OndroMih Aug 29 '16 at 07:08
  • @OndrejM, could you explain how the example works for LockModeType.OPTIMISTIC_FORCE_INCREMENT? Suppose the current version of a Book is 1. When transaction A locks the Book entity, increase its version to 2. Is the new version visible to other transactions? If db isolation level is READ_COMMITTED or stricter, the transaction A needs to be committed for the new version to be visible. At the same time, Transaction B locks the same Book entity, increase its version to 3, How will this guarantee that the book is ended into only one shelf? Thanks. – eastwater Feb 12 '18 at 18:50
  • Here's a [nice explanation](http://www.byteslounge.com/tutorials/jpa-entity-versioning-version-and-optimistic-locking) how optimistic locking works. In short, all concurrent transactions will see Book with version 1 and all would like to commit version 2, but during the update will check if the previous version in DB is 1. Only one of them succeeds. After it commits, all the others will see version 2, detect it and rollback. The check for the correct version in DB is done by the application (JPA framework), not by the DB. – OndroMih Feb 13 '18 at 19:56
  • LockModeType.OPTIMISTIC_FORCE_INCREMENT is basically optimistic lock that will always try to increment the version, even if the entity wasn't changed. Plain OPTIMISTIC lock would increment the version only if the entity is changed, but will do the version check anyway in case another transaction changed the entity. So in short, if Book is used in 2 concurrent transactions, then if OPTIMISTIC lock is in place and no TX modifies the Book, both commit. If OPTIMISTIC and the first of them modifies the book, the second will fail even if it didn't modify the book. – OndroMih Feb 13 '18 at 20:00
  • With OPTIMISTIC_FORCE_INCREMENT, if first TX doesn't modify the Book, the version is still increased and the second transaction will roll back in all cases. – OndroMih Feb 13 '18 at 20:01
  • So OPTIMISTIC lock is semantically similar to a READ lock if entity isn't modified and WRITE lock if it is modified, while OPTIMISTIC_FORCE_INCREMENT is always like a WRITE lock. – OndroMih Feb 13 '18 at 20:02
  • fantastic answer but now JPA has **PESSIMISTIC_WRITE with a javax.persistence.lock.timeout setting of 0** and **PESSIMISTIC_WRITE with a javax.persistence.lock.timeout setting of -2**. Could you add description to this fantastic answer to have a full picture? – gstackoverflow Oct 18 '19 at 17:24
  • What did you mean when you write that **LockModeType.Optimistic is a default** ? AFAIK JPA/Hibernate doesn't use any locking by default – gstackoverflow Oct 20 '19 at 18:28
  • Hi @gstackoverflow, I meant that the Optimistic mode is the default for entities that provide the version field. If they don't, then no locking is used by default. The spec literally says: "If a versioned object is otherwise updated or removed, then the implementation must ensure that the requirements of LockModeType.OPTIMISTIC_FORCE_INCREMENT are met, even if no explicit call to EntityManager.lock was made" Which is effectively plain Optimistic mode. I've updated my answer. – OndroMih Nov 05 '19 at 10:53
  • Hi @OndroMih - as per my understanding LockModeType.PESSIMISTIC_FORCE_INCREMENT needs to be supported by the underneath persistence layer as well to work as intended. Could you please confirm? If yes - is there a good documentation of which databases support this lockmode? I am currently using postgres and am unable to find this information. – Rishabh Sharma Aug 23 '23 at 13:43
  • PESSIMISTIC_FORCE_INCREMENT mode has the same requirements on DB as PESSIMISTiC_WRITE mode. So, yes, the DB needs to support some form of locking, e.g. SELECT FOR UPDATE. It’s the same as PESSIMISTiC_WRITE but also increases the nunber in the version column. You need to check your JPA implementation like Hinernate to see how it supports pessimistic locks in PostgreSQL. – OndroMih Aug 24 '23 at 20:06