1. Overview
In this quick tutorial, we’ll discuss enabling transaction locks in Spring Data JPA for custom query methods and predefined repository CRUD methods.
We will also have a look at different lock types and setting transaction lock timeouts.
2. Lock Types
JPA has two main lock types defined, which are Pessimistic Locking and Optimistic Locking.
2.1 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.
2.2 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.
3. Enabling Transaction Locks on Query Methods
To acquire a lock on an entity, we can annotate the target query method with a Lock annotation by passing the required lock mode type.
Lock mode types are enum values to be specified while locking an entity. The specified lock mode is then propagated to the database to apply the corresponding lock on the entity object.
To specify a lock on a custom query method of a Spring Data JPA repository, we can annotate the method with @Lock and specify the required lock mode type:
@Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT) @Query("SELECT c FROM Customer c WHERE c.orgId = ?1") public List<Customer> fetchCustomersByOrgId(Long orgId);
To enforce the lock on predefined repository methods such as findAll or findById(id), we have to declare the method within the repository and annotate the method with the Lock annotation:
@Lock(LockModeType.PESSIMISTIC_READ) public Optional<Customer> findById(Long customerId);
When the lock is explicitly enabled and there is no active transaction, the underlying JPA implementation will throw a TransactionRequiredException.
In case the lock cannot be granted and the locking conflict doesn’t result in a transaction rollback, JPA throws a LockTimeoutException. But it doesn’t mark the active transaction for rollback.
4. Setting Transaction Lock Timeouts
When using Pessimistic Locking, the database will try to lock the entity immediately. The underlying JPA implementation throws a LockTimeoutException when the lock cannot be obtained immediately. To avoid such exceptions, we can specify the lock timeout value.
In Spring Data JPA, the lock timeout can be specified using the QueryHints annotation by placing a QueryHint on query methods:
@Lock(LockModeType.PESSIMISTIC_READ) @QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value = "3000")}) public Optional<Customer> findById(Long customerId);
Further details on setting the lock timeout hint at different scopes can be found in this ObjectDB article.
5. Conclusion
In this tutorial, we’ve learned the different types of transaction lock modes. We’ve learned how to enable transaction locks in Spring Data JPA. We’ve also covered setting lock timeouts.
Applying the right transaction locks at the right places can help to maintain data integrity in high-volume concurrent usage applications.
When the transaction needs to adhere to ACID rules strictly, we should use Pessimistic Locking. Optimistic Locking should be applied when we need to allow multiple concurrent reads and when eventual consistency is acceptable within the application context.
Of course, sample code for both Pessimistic Locking and Optimistic Locking can be found over on Github.