JPA: How does Read Lock work? - java

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.

Related

Hibernate JPA - PESSIMISTIC_WRITE, read the locked record

I was reading this article https://www.baeldung.com/jpa-pessimistic-locking, where it's written that
PESSIMISTIC_WRITE – allows us to obtain an exclusive lock and prevent the data from being read, updated, or deleted
So I tried to confirm this.
In Transaction(TX1), I took a PESSIMISTIC_WRITE lock on an entity1. Then called a Thread.sleep(5).
While TX1 is going on, I did a GET API call on the same entity1, which returned the data with 200 status. After which TX1 got completed.
Maybe my implementation is wrong here, Could anyone help me in understanding this?
Resources used: JAVA 8, Mysql version: 5.6.34, Engine: InnoDB
If I'm able to read a locked entity, then this may lead to a dirty update.
So in TX2, I did a plain findById and then did an update on entity1. Here TX2 is waiting till TX1 is completed because of locking but TX2 will do a dirty update as it is not aware of the changes made by TX1.

How acquiring shared and exclusive locks works in Hibernate

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

What happens during concurrent access with JPA and exclusive locks?

We are currently doing a discussion on our architecture.
The idea is that we have a database and multiple processing units. One transaction does include the following steps:
Query an entry based on a flag
Request exclusive lock for this entry
Do the processing
Update the Flag and some other columns
Release lock and commit transaction
But what happens if the second processing unit queries an entry, while the first one does hold a lock?
Updating the flag during transaction does not do the job due to transaction isolation and dirty read
In my opinion the possible results on this situation are:
The second processing unit gets the same entry and an exception is raised during the lockrequest
The second one get the next available entry and everything is fine.
The second processing unit gets the same entry.
However, whether the exception will be thrown or the lock acquisition will be blocked until the lock is released by the first transaction depends on the way you are asking for the lock in the second transaction (for example, with timeout or NO WAIT or something similar).
The second scenario (The second one gets the next available entry) is a bit harder to implement. Concurrent transactions are isolated from each other, so they basically see the same snapshot of data until one of them commits.
You can take a look at some database specific features, like Oracle Advanced Queue, or you could change the approach you read data (for example, read them in batches and then dispatch the processing to multiple threads/transactions). All of this highly depends on what exactly you are solving, are there any processing order constraints, failure/rollback/retry handling, etc.

JPA - Pessimistic Lock - What happens when the lock exists?

Background Info:
I have an issue that is symptomatic of an entity update not going through. Reviewing my logs, I can see see the update sql statements that I expected, but they are almost simultaneous (0.012 seconds apart) and the application uses a pessimistic read lock when updating the entity.
That leads me to my question:
What is the expected behavior when a pessimistic lock exists? Should I still expect to see multiple update queries? I should expect the PessimisticLockException to be thrown, right? Are there any other indicators I should look for?
Hibernate is my JPA implementation.
Pesimistic locks are actually propagated to the DB level using SQL-queries (check the executed queries to compare).
If a pessimistic lock exists, the application should wait for the DB until the lock is released, so it is not mandatory an expcetion to be thrown (but it could be).
Now about the exceptions:
/*
PessimisticLockException if pessimistic locking fails and the transaction is rolled back
LockTimeoutException if pessimistic locking fails and only the statement is rolled back
*/
public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode);
For other EntityManager methods those two exceptions are thrown in simmilar situations.
Pessimistic locking prevents objects from being updated simultaneously. Instead, the object's updates are forming the sort of chain - if the lock already exists, the update will wait until the lock is released.
Thus, throwing an exception is not expected outcome of pessimistic lock. Expected behavior is eliminating of concurrency I described above.
For further reading you can refer to this and this sources.
In our case it seems that your update is not going through because it is overwritten by some later update.

How to release a locked row using JPA?

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.

Categories