JPA Buddy
announcement - icon

JPA is huge! It covers nearly every aspect of communication between relational databases and the Java application and is deeply integrated into all major frameworks.

If you're using IntelliJ, JPA Buddy is super helpful. The plugin gently guides you through the subtleties of the most popular JPA implementations, visually reminds you of JPA features, generates code that follows best practices, and integrates intelligent inspections to improve your existing persistence code.

More concretely, it provides powerful tooling to generate Spring Data JPA repositories and methods, Flyway Versioned Migrations, Liquibase Differential Changelogs, DDL and SQL statements, DTO objects, and MapStruct interfaces.

Oh, and it actually generates JPA entities from an existing database and gradually update the data model as the database evolves! Yeah.

>> Become a lot more productive with JPA Buddy

Persistence top

Get started with Spring Data JPA through the reference Learn Spring Data JPA course:


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:

@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:

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:

@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, the sample code for both Pessimistic Locking and Optimistic Locking can be found over on Github.

Persistence bottom
Get started with Spring Data JPA through the reference Learn Spring Data JPA course: >> CHECK OUT THE COURSE
Persistence footer banner
Comments are closed on this article!