I found that the #Transactional is used to ensure transaction on repository method or on a service method.
#Lock is used on repository method to ensure locking of entity to provide isolation.
Some questions are raised in my mind:
What are major difference/relations in these two annotations ?
When to use #Transactional and when to use #Lock ?
Is #Lock useful in distributed database system to provide data concurrency and consistency ?
Transactional: Whenever you put #Transactional annotation, it enables transactional behavior which qualifies ACID properties
ACID: ACID (Atomicity, Consistency, Isolation, Durability) is a set of
properties of database transactions intended to guarantee the validity
even in the event of errors.
Atomic
Guarantees that all operations in a transaction are treated as a single “unit”, which either succeeds completely or fails completely.
Consistent
Ensures that a transaction can only bring the database from one valid state to another by preventing data corruption.
Isolation
Determines how and when changes made by one transaction become visible to the other. Serializable and Snapshot Isolation are the top 2 isolation levels from a strictness standpoint.
Durable
Ensures that the results of the transaction are permanently stored in the system. The modifications must persist even in case of power loss or system failures.
Lock: It should not be confused with transactional,#Lock enables locking behavior during a transaction
JPA has two main lock types defined.
Pessimistic Locking
Optimistic Locking
If you want to know more about Pessimistic and Obtimistic locking you can explore the internet, below is explanation from Baeldung,
Pessimistic Locking When we are using Pessimistic Locking in a
transaction and access an entity, it will be locked immediately. The
transaction releases the lock either by committing or rolling back the
transaction.
Optimistic Locking In Optimistic Locking, the transaction doesn't lock
the entity immediately. Instead, the transaction commonly saves the
entity's state with a version number assigned to it.
When we try to update the entity's state in a different transaction,
the transaction compares the saved version number with the existing
version number during an update.
At this point, if the version number differs, it means that the entity
can't be modified. If there is an active transaction then that
transaction will be rolled back and the underlying JPA implementation
will throw an OptimisticLockException.
Apart from the version number approach, we can use other approaches
such as timestamps, hash value computation, or serialized checksum,
depending on which approach is the most suitable for our current
development context.
There are also other lock types available in spring
NONE: No lock.
OPTIMISTIC: Optimistic lock.
OPTIMISTIC_FORCE_INCREMENT: Optimistic lock, with version update.
PESSIMISTIC_FORCE_INCREMENT: Pessimistic write lock, with version update
PESSIMISTIC_READ: Pessimistic read lock.
PESSIMISTIC_WRITE: Pessimistic write lock.
READ: Synonymous with OPTIMISTIC.
WRITE: Synonymous with OPTIMISTIC_FORCE_INCREMENT.
Now answer to your questions
What are the major differences/relations in these two annotations?
You will understand after reading above
When to use #Transactional and when to use #Lock?
If you want transactional behavior then add #transactional and if your usecase requires locking and as per use case use appropriate locking
Is #Lock useful in the distributed database system to provide data
concurrency and consistency?
The two main tools we use to cope with concurrency are database transactions and distributed locks. These two are not interchangeable. You can't use a transaction when you need a lock. You can't use a lock when you need a transaction. source
Related
I have a #Transactional method where I request a record using findById() from a Spring Data repository.
Now I want to lock this object in a way that other #Transactional methods if executed in parallel will wait until the lock is released.
What I tried: I went through the documentation of #Lock and LockModeType but I still can't figure out if it covers my case.
Another option would be select for update on a DB level, but I am not sure it is the best option.
Q: How to lock an Entity object (db row) in a spring data transaction and make other transactions wait?
What you want is called Pessimistic locking. Spring Data through JPA specification supports that. There are two modes of pessimistic locking available:
I think you are mixing up Transaction isolation with Locking. Through #Transactional annotation you can specify isolation which is applied to the connection, while through spirng-data #Locking you can define the locking mode. The Lock mode is not related to the transaction isolation specified on #Transactional. Effectively if the isolation is SERIALIZEABLE you would not need locking via JPA or spring-data, because the database will take care of it. The locks will be DB controlled in this case.
When you define locking the control is in your hands. Here is a description of the pessimistic lock types.
PESSIMISTIC_WRITE - all transactions including the read only ones will be blocked until the transaction holding the lock is complete. Dirty reads are not allowed.
PESSIMISTIC_READ - when you have this mode , concurrent transactions can read the data of the locked row. All updates need to obtain PESSIMISTIC_WRITE lock.
On top of this you can specify a timeout for the locking transaction completion.
In case of hibernate jpa LockModeType.OPTIMISTIC_FORCE_INCREMENT, is this lock taken at application level or database level.
I am using following snippet for taking optimistic locks:
setting = this.entityManager.find(Setting.class, setting.getId(),
LockModeType.OPTIMISTIC_FORCE_INCREMENT);
setting.setUpdateTimestamp(new Date());
newSettingList.add(setting);
Suppose there are two jvm's running and both have same methods and there is conflict, will this locking mechanism work in this case?
My observation is that, whenever I was debugging and "newSettingList.add(setting);" at this line in code, I was not seeing any changes in database at that point. So how locking is ensured at database level?
Optimistic Locking is a strategy where you read a record, use version number to check that the version hasn't changed before you write the record back. When you write the record back you filter the update on the version to make sure it's atomic.
Pessimistic Locking is when you lock the record for your exclusive use until you have finished with it. It has much better integrity than optimistic locking but requires you to be careful with your application design to avoid Deadlocks.
Better explanation here.
This means that since you use optimistic locking you don't intervene in the locks at the database level. What you you do is simply use the database to keep versioning of the objects-entities. For example:
a) You open a T1 transaction from the 1st jvm and read an object with version v1.
b) You open a T2 transaction from the 2nd jvm and read the same object with v1.(no update in this object has been made).
c) You update in T1 transaction the object and setting its version v2. you commit the transaction.
d) You try to have access again in db for the object but you get an exception because of the versioning.
But there is no need to have access from the same jvm for the 2 transactions
I have read about transaction isolation levels. It is used to prevent parallel transaction executions errors. Its quite obvious.
There are also locking modes available for entities. I understand how they work.
But I cant find the reason why I need locking? I have used transaction isolation levels already. Why do I have to use locking?
Do isolation levels and locking make the same job?
Both transaction isolation and JPA Entity locking are concurrency control mechanisms.
The transaction isolation is applied on a JDBC Connection level and the scope is the transaction life-cycle itself (you can't change the transaction isolation from your current running transactions). Modern databases allow you to use both 2PL (two-phase locking) isolation levels and MVCC ones (SNAPSHOT_ISOLATION or PostgreSQL isolation levels). In MVCC, readers do not block writers and writers do not block readers (only writers block writers).
The Java Persistence Locking API offers both database-level and application-level concurrency control, which can be split in two categories:
Explicit Optimistic lock modes:
OPTIMISTIC
OPTIMISTIC_FORCE_INCREMENT
PESSIMISTIC_FORCE_INCREMENT
The optimistic locking uses version checks in UPDATE/DELETE statements and fail on version mismatches.
Explicit pessimistic lock modes:
PESSIMISTIC_READ
PESSIMISTIC_WRITE
The pessimistic lock modes use a database-specific lock syntax to acquire read (shared) or write (exclusive) locks (eg. SELECT ... FOR UPDATE).
An explicit lock mode is suitable when you run on a lower-consistency isolation level (READ_COMMITTED) and you want to acquire locks whose scope are upgraded from query life-time to a transaction life-time.
Introduction
There are different locking types and isolation levels. Some of the locking types (OPTIMISTIC*) are implemented on the JPA-level (eg. in EclipseLink or Hibernate), and other (PESSIMISTIC*) are delegated by the JPA-provider to the DB level.
Explanation
Isolation levels and locking are not the same, but they may intersect somewhere. If you have the SERIALIZED isolation level (which is performance-greedy), then you do not need any locking to do in JPA, as it is already done by the DB. On the other side, if you choose READ_COMMITTED, then you may need to make some locking, as the isolation level alone will not guarantee you e.g that the entry is not changed in the meanwhile by another transaction.
Edit:
It turns out in this case since I was using "version" annotation, so I'm using optimistic locking, not pessimistic locking.
If I remove version and hence disable optimistic locking. The pessimistic locking takes over and performance degraded significantly.
So I guess I have to live with optimistic locking and occasional exceptions. Is there a better solution?
Original:
I currently have multiple tomcat instances in an apache 2.2 load balancer via ajp. The backend system is hibernate. The system serves multiple users and requests, for request it deducts one credit from the user's account.
I use hibernate's pessimistic locking for managing credit deductions.
I kept getting the following from time to time on user account object in the logs:
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
Code
private boolean decUserAccountQuota(UserAccount userAccount, int creditDec) {
if(userAccount.getBalance() <1) return false;
String userName = userAccount.getUsername();
Manager manager = getManager(userName);
try{
manager.beginTransaction();
manager.refresh(userAccount, LockMode.UPGRADE); //this uses pessimistic locking, this calls sessionFactory.getCurrentSession().refresh();
userAccount.setBalance(userAccount.getBalance()-creditDec);
manager.commitTransaction(); //this causes the Exception
}catch(Exception exp){
exp.printStackTrace();
manager.rollbackTransaction();
return false;
}finally{
manager.closeSession();
}
return true;
}
Questions:
How do I prevent this exception from happening. What happens here
is more than one threads tries to update the same entity, one thread
succeeds and hence, when the next thread goes to commit the data, it
sees that its already been modified and ends up throwing
StaleObjectStateException. But if I'm already using pessimistic
locking, how can the exception still happen?
Are there any better ways in terms of performance and integrity in
managing the user account credit system?
Your Hibernate entity is either using #Version annotation or defines <version> in your XML Hibernate mapping. This will enable the optimistic locking provided by Hibernate.
If you are explicitly using pessimistic locking as you described, removing the above should fix your problem.
More info here
In your code the below line is not taking a pessimistic lock, although you are specifying it to, because your database doesnot support SELECT.. FOR UPDATE .
manager.refresh(userAccount, LockMode.UPGRADE);
For this reason hibernate uses an alternate mode of locking called LockMode.READ for userAccount which is optimistic locking based on the #Version attribute in your entity.
I searched to see if there is any alternative way of forcing hibernate to take pessimistic locking for your scenario but it looks like its not possbile.
Coming back to your question of how to minimize or avoid the StaleObjectStateException here are my thoughts:-
Synchronize on userAccount object . Though this seems to affect performance but this would happen only if the same user places too many concurrent requests. Also giving up a little on performance to make sure that user need not be thrown an exception and made to retry would be ideal scenario.
If you have found any alternative solution please do share it .
I am using Java EE 6 with JBOSS7 and JPA2 + Hibernate. For my client I provide a REST api.
My concern is how to efficiently ensure that no resources where modified concurrently. Should happen too often, but in case it happens I would like to ensure proper handling.
My approaches so far:
Map<String, ReentrantLock> to store the locks. (my ids are always
UUIDs) Locks are created on demand if missing in map. On this
approach i like that concurrent access will be blocked and i can
control how long the other thread tries to lock the resource.
Use JPA2 optimistic locking.
Which one would you recommend? Or is there an even better approach?
seems error-prone, plus it might not scale. I've never seen such
design and would discourage it.
transactions with optimistic
locking is a viable option. In this case, some transaction might
fail and you will need to deal with errors and retry.
transactions with pessimistic locking is another viable option. It's
like 1) but using the database to lock and order operations. AFAIK,
JPA support pessimistic locking as well. Otherwise you can use
SELECT FOR UPDATE(supported by most DBMS) to explicitely acquire row locks. Make sure you
figure out a scheme were locks are acquired in consistent order, to
avoid deadlocks.
The choice between 2-3 depends on the use case, e.g. if contention is expected to be high or not, or whether it is easy to retry a failed transaction.