Reputation: 586
I found a lot of posts regarding this topic, but all answers were just links to documentations with no example code, i.e., how to use concurrency in practice.
My situation: I have an entity House
with (for simplyfication) two attributes, number
(the id) and owner
. The database is initialized with 10 Houses
with number
1-10 and owner
always null
.
I want to assign a new owner to the house with currently no owner, and the smallest number
. My code looks like this:
@Transactional
void assignNewOwner(String newOwner) {
//this is flagged as @Transactional too
House tmp = houseDao.getHouseWithoutOwnerAndSmallestNumber();
tmp.setOwner(newOwner);
//this is flagged as @Transactional too
houseDao.update(tmp);
}
For my understanding, although the @Transactional
is used, the same House
could be assigned twice to different owners, if two requests fetch the same empty House
as tmp
. How do I ensure this can not happen?
I know, including the update in the selection of the empty House
would solve the issue, but in near future, I want to modify/work with the tmp
object more.
Upvotes: 5
Views: 876
Reputation: 120848
Actually it's pretty easy - at least in my opinion and I am going to abstract away of what Hibernate
will generate when you say Pessimistic/Optimistic
. You might think this is SELECT FOR UPDATE
- but it's not always the case, MSSQL AFAIK does not have that...
These are JPA
annotations and they guarantee some functionality, not the implementation.
Fundamentally they are entire different things - PESSIMISTIC
vs OPTIMISTIC
locking. When you do a pessimistic locking you sort of do a synchronized block
at least logically - you can do whatever you want and you are safe within the scope of the transaction. Now, whatever the lock is being held for the row, table or even page
is un-specified; so a bit dangerous. Usually database may escalate locks, MSSQL does that if I re-call correctly.
Obviously lock starvation is an issue, so you might think that OPTIMISTIC
locking would help. As a side note, this is what transactional memory is in modern CPU
; they use the same thinking process.
So optimistically locking is like saying - I will mark this row with an ID/Date, etc, then I will take a snapshot of that and work with it - before committing I will check if that Id has a changed. Obviously there is contention on that ID
, but not on the data. If it has changed - abort (aka throw OptimisticLockException
) otherwise commit the work.
The thing that bothers everyone IMO is that OptimisticLockException
- how do you recover from that? And here is something you are not going to like - it depends. There are apps where a simple retry would be enough, there are apps where this would be impossible. I have used it in rare scenarios.
I usually go with Pessimistic
locking (unless Optimistic
is totally not an option). At the same time I would look of what hibernate generates for that query. For example you might need an index
on how the entry is retrieved for the DB
to actually lock just the row - because ultimately that is what you would want.
Upvotes: 1
Reputation: 26502
Optimistic
If you add a version column to your entity / table then you could take advantage of a mechanism called Optimistic Locking. This is the most proficient way of making sure that the state of an entity has not changed since we obtained it in a transactional context.
Once you createQuery
using the session
you can then call setLockMode(LockModeType.OPTIMISTIC);
Then, just before the transaction is commited, the persistence provider would query for the current version of that entity and check whether it has been incremented by another transaction. If so, you would get an OptimisticLockException and a transaction rollback.
Pessimistic
If you do not version your rows, then you are left with pessimistic lockin which basically means that you phycically create a lock for queries entities on the database level and other transactions cannot read / update those certain rows.
You achieve that by setting this on the Query
object:
setLockMode(LockModeType.PESSIMISTIC_READ);
or
setLockMode(LockModeType.PESSIMISTIC_WRITE);
Upvotes: 1