I try to understand the way READ COMMITED and READ UNCOMMITED isolation levels works in Hibernate and need some explanation.
There are 2 threads THR1 and THR2 both executing the same transactional method (Spring Transactional annotation with isolation level set to READ COMMITED). Name transactions created be these threads TRA1 and TRA2 accordingly. The transactional method looks as follows:
public void updateOrSavePreference(String name, String value) {
Preference preferenceToUpdate = findPreferenceUsingNamedQuery(name); // line 1. shared read lock acquired (uses HibernateTemplate.findByNamedQueryAndNamedParam(...))
if (preferenceToUpdate != null) { // line 2.
preferenceToUpdate.setValue(value); // line 3. exclusive write lock acquired (actually I use the HibernateTemplate.merge(...) method
// instead a setter because the entity type is immutable, but it seems irrelevant for this example)
} else { // line 4.
HibernateTemplate.save(preferenceToUpdate); // line 5. exclusive write lock acquired
}
}
The Preference class is annotated with Entity(optimisticLock = OptimisticLockType.NONE) to enforce 2PL model for this entity (am I wrong?). I use Oracle database.
Consider following scenarios:
Let's assume that thread THR1 steps to line 1 and queries an object. If I understand correctly, the transaction TRA1 created by this thread acquires a shared read lock for the queried entiry. Then, if the THR2 thread steps to line 3 trying to acquire an exclusive write lock for this entity shouldn't THR2 be blocked until TRA1 releases the read lock?
Let's assume that thread THR1 steps to line 3 and acquires an exclusive write lock for an entity (exclusive lock is held until the TRA1 transaction completes). Then, the THR2 thread steps to line 1 and tries to query this entity. Shouldn't THR2 be blocked because the TRA2 transaction tries to acquire a read lock while other transaction TRA1 holds an exclusive write lock for this entity?
If I reproduce the scenario from point 2 for the READ UNCOMMITED isolation level, THR2 executing the TRA2 transaction doesn't see changes made by THR1 in the TRA1 transaction even after refreshing or querying the entity again ("evaluate expression" under debug). Why?
Technically read committed can be achieved by setting read locks. But not necessarily. If your DBMS supports MVCC you always read committed data (except what was changed in your own transaction) without setting a lock.
So I suspect you do your tests using oracle, mysql (INNODB) or postgres? All these DBMS support MVCC as default, so they never set shared read locks.
Since you are using Oracle "the MVCC" Database there will not be implemented 2PL-protocol even if you configure that at your entity. If you want to find out what really is done on your DBMS in native statements just activate the outputting of the native-statements as you can do in persistence.xml:
<property name="hibernate.show_sql" value="true" />
Perhaps you should also have a look at transaction-isolation-levels-relation-with-locks-on-table
or first at: locks and oracle
Related
I am learning JPA pessimistic lock. I found the following explanation
PESSIMISTIC_READ - The Entity is locked on the database, prevents any
other transaction from acquiring a PESSIMISTIC_WRITE lock.
PESSIMISTIC_WRITE - The Entity is locked on the database, prevents any
other transaction from acquiring a PESSIMISTIC_READ or
PESSIMISTIC_WRITE lock.
If I understand it right, then if we have three users (A, B, C) and user A gets READ lock, then user B can get READ lock too, but user C can't get WRITE lock until users A and B releases their locks. And if user A gets a WRITE lock then user B and user C can't get nothing until user A releases the lock.
However, for my client-server application I want the following logic. If users want only to read an entity their open the entity in READ-ONLY mode (unlimited number of users can do it at the same time). If some user wants to edit the entity he opens it in WRITE mode - no one can open the same entity in WRITE mode (until the user releases the WRITE lock) but all other can still open the entity in READ-ONLY mode.
And I have two questions:
Is my understanding of JPA pessimistic lock right?
Is it possible to make JPA do the logic I need (using JPA lock mechanisms)?
Is my understanding of JPA pessimistic lock right?
Yes, that's exactly how read/write locking works
...but all other can still open the entity in READ-ONLY mode
I'm not exactly sure what you mean. We are still talking about multiple transactions executing simultaneously, right (I have a strange feeling that's not what you mean)? If that's the case, then in your logic, holding a 'READ_ONLY' lock accomplishes nothing.
Locking means 'I'm freezing this resource so that certain other transactions cannot proceed until I'm done'. But, in the logic you described, when you're holding the 'READ_ONLY' lock, both a transaction holding the 'READ_ONLY' lock and the transaction holding the 'WRITE' lock are allowed to proceed.
I'm developing an application with JPA2.1. I have the followed trouble.
I'm trying to lock an entity in this way :
Book book = em.find(Book.class, 12);
em.lock(book, LockModeType.PESSIMISTIC_WRITE);
but if try to access from another windows browser or client to entity with id=12 , the system doesn't thrown PessimisticLockException?
Where am I wrong?
The lock will be effective during the lifetime of the transaction but certainly not across multiple request-response loop (unless you have configured your entity manager and transaction manager to manage long time transaction).
The transaction MUST be a short-time living object (for performance reasons).
Optimistic write-lock means that book will not be modified by any other thread between the lock instruction and the end of the transaction. But the book object itself may live longer of course.
I suppose that in another window/browser you try the same thing: to acquire a PESSIMISTIC_WRITE lock.
The problem that you have, is that the lock is released when the method returns (as the transaction ends), meaning that when you open the second browser/window, there is no lock anymore.
You should probably explain us the problem/scenario that you want to try to solve/test.
For the general situation:
Another possible cause could be that your database table does not support row-level locking. For example in MySql only the InnoDB storage engine supports "SELECT * FOR UPDATE" (which the PESSIMISTIC_WRITE lock is translated into).
I am looking at EntityManager API, and I am trying to understand an order in which I would do a record lock. Basically when a user decides to Edit a record, my code is:
entityManager.getTransaction().begin();
r = entityManager.find(Route.class, r.getPrimaryKey());
r.setRoute(txtRoute.getText());
entityManager.persist(r);
entityManager.getTransaction().commit();
From my trial and error, it appears I need to set WWEntityManager.entityManager.lock(r, LockModeType.PESSIMISTIC_READ); after the .begin().
I naturally assumed that I would use WWEntityManager.entityManager.lock(r, LockModeType.NONE); after the commit, but it gave me this:
Exception Description: No transaction is currently active
I haven't tried putting it before the commit yet, but wouldn't that defeat the purpose of locking the record, since my goal is to avoid colliding records in case 50 users try to commit a change at once?
Any help as to how to I can lock the record for the duration of the edit, is greatly appreciated!
Thank You!
Performing locking inside transaction makes perfectly sense. Lock is automatically released in the end of the transaction (commit / rollback). Locking outside of transaction (in context of JPA) does not make sense, because releasing lock is tied to end of the transaction. Also otherwise locking after changes are performed and transaction is committed does not make too much sense.
It can be that you are using pessimistic locking to purpose other than what they are really for. If my assumption is wrong, then you can ignore end of the answer. When your transaction holds pessimistic read lock on entity (row), following is guaranteed:
No dirty reads: other transactions cannot see results of operations you performed to locked rows.
Repeatable reads: no modifications from other transactions
If your transaction modifies locked entity, PESSIMISTIC_READ is upgraded to PESSIMISTIC_WRITE or transaction fails if lock cannot be upgraded.
Following coarsely describes scenario with obtaining locking in the beginning of transaction:
entityManager.getTransaction().begin();
r = entityManager.find(Route.class, r.getPrimaryKey(),
LockModeType.PESSIMISTIC_READ);
//from this moment on we can safely read r again expect no changes
r.setRoute(txtRoute.getText());
entityManager.persist(r);
//When changes are flushed to database, provider must convert lock to
//PESSIMISTIC_WRITE, which can fail if concurrent update
entityManager.getTransaction().commit();
Often databases do not have separate support for pessimistic read, so you are actually holding lock to row since PESSIMISTIC_READ. Also using PESSIMISTIC_READ makes sense only if no changes to the locked row are expected. In case above changes are done always, so using PESSIMISTIC_WRITE from the beginning on is reasonable, because it saves you from the risk of concurrent update.
In many cases it also makes sense to use optimistic instead of pessimistic locking. Good examples and some comments about choosing between locking strategies can be found from: Locking and Concurrency in Java Persistence 2.0
Great work attempting to be safe in write locking your changing data. :) But you might be going overboard / doing it the long way.
First a minor point. The call to persist() isn't needed. For update, just modify the attributes of the entity returned from find(). The entityManager automatically knows about the changes and writes them to the db during commit. Persist is only needed when you create a new object & write it to the db for the first time (or add a new child object to a parent relation and which to cascade the persist via cascade=PERSIST).
Most applications have a low probability of 'clashing' concurrent updates to the same data by different threads which have their own separate transactions and separate persistent contexts. If this is true for you and you would like to maximise scalability, then use an optimistic write lock, rather than a pessimistic read or write lock. This is the case for the vast majority of web applications. It gives exactly the same data integrity, much better performance/scalability, but you must (infrequently) handle an OptimisticLockException.
optimistic write locking is built-in automatically by simply having a short/integer/long/TimeStamp attribute in the db and entity and annotating it in the entity with #Version, you do not need to call entityManager.lock() in that case
If you were satisfied with the above, and you added a #Version attribute to your entity, your code would be:
try {
entityManager.getTransaction().begin();
r = entityManager.find(Route.class, r.getPrimaryKey());
r.setRoute(txtRoute.getText());
entityManager.getTransaction().commit();
} catch (OptimisticLockException e) {
// Logging and (maybe) some error handling here.
// In your case you are lucky - you could simply rerun the whole method.
// Although often automatic recovery is difficult and possibly dangerous/undesirable
// in which case we need to report the error back to the user for manual recovery
}
i.e. no explicit locking at all - the entity manager handles it automagically.
IF you had a strong need to avoid concurrent data update "clashes", and are happy to have your code with limited scalability then serialise data access via pessimistic write locking:
try {
entityManager.getTransaction().begin();
r = entityManager.find(Route.class, r.getPrimaryKey(), LockModeType.PESSIMISTIC_WRITE);
r.setRoute(txtRoute.getText());
entityManager.getTransaction().commit();
} catch (PessimisticLockException e) {
// log & rethrow
}
In both cases, a successful commit or an exception with automatic rollback means that any locking carried out is automatically cleared.
Cheers.
I am trying to understand whats the effect of calling EntityManager.lock(entity, LockModeType.READ). The API documentation sounds very confusing for me.
If I have to concurrent threads and Thread 1 calls lock(entity, LockModeType.READ), can Thread 2 still read and write the entity?
What I have learned so far:
The lock type READ in JPA1 is the same as OPTIMISTIC in JPA2. If such a lock is set, the EntityManager checks the version attribute before commiting the transaction, but does not update it. I found an explanation for the OPTIMISTIC lock mode: Link. Search for OPTIMISTIC (READ) LockMode Example.
As fas as I understand this, setting a read lock in Thread 1 has no effect on Threads 2 ... n. All other threads can still read and write the entity. But when the transaction in Thread 1 commits and an other Thread has updated the entity, the transaction in Thread 1 is rolled back.
Am I understanding this correct?
Read is curently deprecated anyway but just for your understanding:
A READ lock will ensure that the state of the object does not change on commit, because the READ lock allows other transactions to update or delete it then if Thread 1 does some change and then commits it first checks the state (the version) of the entity if it checks, it is commited, if not it is not allowed,
so basicly your understanding is correct.
there is also OPTIMISTIC_READ which is the modern way of using it(aslo there is _WRITE).
UPDATE
Ok this article helped me a lot in understanding hope this helps.
I'm using the EclipseLink implementation of the JPA 2.0 which allows pessimistic locking. I know how to lock an entity but how do I release the lock? At first I thought this was all taken care of within a transaction (in other words, the entity is locked until you commit the transaction), but that does not seem to be the case.
I tried a quick google search (seems like this should be pretty obvious), but I haven't found anything...
After getting some sleep... and doing some more testing in the morning, I believe I have figured out my problem.
So the lock is actually taken care of within a transaction. However, when I was testing my code, I was able to retrieve a locked row using the EntityManager.find(Class, key) method (no locking strategy specified).
I erroneously thought that by putting a lock on a row, the row could not be read, period. However, I reread the JPA definitions of PESSIMISTIC_READ and PESSIMISTIC_WRITE and noticed my problem:
PESSIMISTIC_READ - The Entity is locked on the database, prevents any other transaction from acquiring a PESSIMISTIC_WRITE lock.
PESSIMISTIC_WRITE - The Entity is locked on the database, prevents any other transaction from acquiring a PESSIMISTIC_READ or PESSIMISTIC_WRITE lock.
The lock doesn't necessarily prevent all reads, it just prevents another transaction from putting a READ or WRITE lock on the row.