Spring Data JPA entity locking
Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.
So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!
Introduction
In this article, we are going to see what options Spring Data JPA offers for entity locking.
We will see how we can apply a shared or exclusive row-level lock upon fetching one or multiple entities.
JPA LockModeType
As I explained in this article, JPA offers several explicit locking options that can be applied at entity fetching time via the LockModeType
enumeration.
If you want to apply a pessimistic lock, then you can use one of the following options:
LockModeType.PESSIMISTIC_READ
to apply a shared or read lock on the associated table records.LockModeType.PESSIMISTIC_WRITE
to apply an exclusive or write lock on the associated table records.
If the underlying database does not support shared locks, then the PESSIMISTIC_READ
strategy will fall back to a PESSIMISTIC_WRITE
since this option is widely supported.
Direct entity locking
If we want to lock an entity upon fetching it, then the JPA EntityManager
offers a find
method variant that allows us to pass the LockModeType
:
Post post = entityManager.find(Post.class, id, lockMode);
However, while the Spring Data JpaRepository
doesn’t offer this option, we can easily add it using a custom Spring Data Repository.
For instance, we can have our PostRepository
extend a CustomPostRepository
beside the JpaRepository
:
@Repository public interface PostRepository extends JpaRepository<Post, Long>, CustomPostRepository { }
In the CustomPostRepository
interface, we can define a lockById
method like this:
public interface CustomPostRepository { Post lockById(Long id, LockModeType lockMode); }
While Spring Data JPA can provide an implementation for query methods or methods annotated with the @Query
annotation, for our CustomPostRepository
interface, we will have to provide the CustomPostRepositoryImpl
implementation that Spring Data JPA will use when creating the PostRepository
Java object instance at runtime.
The CustomPostRepositoryImpl
looks as follows:
public class CustomPostRepositoryImpl implements CustomPostRepository { @PersistenceContext private EntityManager entityManager; @Override public Post lockById(Long id, LockModeType lockMode) { return entityManager.find(Post.class, id, lockMode); } }
Having the implementation of the lockById
method in place, we can now test it.
To apply a shared lock on PostgreSQL, we will have to use the LockModeType.PESSIMISTIC_READ
strategy, like this:
Post postWithSharedLock = postRepository.lockById( 1L, LockModeType.PESSIMISTIC_READ );
And, Hibernate will execute the following SQL query:
SELECT p.id AS id1_0_0_, p.slug AS slug2_0_0_, p.title AS title3_0_0_ FROM post p WHERE p.id = 1 FOR SHARE
To acquire an exclusive lock, we can pass the PESSIMISTIC_WRITE
option:
Post postWithExclusiveLock = postRepository.lockById( 2L, LockModeType.PESSIMISTIC_WRITE );
And, Hibernate will use a FOR UPDATE
clause instead:
SELECT p.id AS id1_0_0_, p.slug AS slug2_0_0_, p.title AS title3_0_0_ FROM post p WHERE p.id = 2 FOR UPDATE
Query entity locking
We can also apply row-level locks to all the records returned by a given SQL query, and for that, Spring Data JPA offers the @Lock
annotation.
For instance, in the PostCommentRepository
, we can define a lockAllByPostId
method that applies a read lock on all the child PostComment
rows that are associated with a given Post
parent record:
@Repository public interface PostCommentRepository extends JpaRepository<PostComment, Long> { @Query(""" select pc from PostComment pc where pc.post.id = :postId """) @Lock(LockModeType.PESSIMISTIC_READ) List<PostComment> lockAllByPostId( @Param("postId") Long postId ); }
And when executing the lockAllByPostId
method:
List<PostComment> commentWithLock = postCommentRepository.lockAllByPostId(1L);
Hibernate will run the following SQL query on PostgreSQL:
SELECT pc.id as id1_1_, pc.post_id as post_id3_1_, pc.review as review2_1_ FROM post_comment pc WHERE pc.post_id = 1 FOR SHARE OF pc
Cool, right?
I'm running an online workshop on the 11th of October about High-Performance SQL.If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
Locking entities is very easy with Spring Data JPA, and it works for both direct entity fetching as it does for queries.
In fact, I think it would be a good idea to add the lockById
method to the BaseJpaRepository
utility offered by the Hypersistence Utils project.

How about explicit optimistic locking in Spring Data JPA? Previously, you said there is always a risk of optimistic locking race conditions (between the version check and committing the transaction). Does it mean that if we want to apply an explicit optimistic locking upon entity fetching in the same manner as it was shown in this article, then we might do need some additional functionality (such as mentioned in the first comment – adding a separate lock method), so that we can use it to set a LockModeType.PESSIMISTIC_READ in the end of each transaction where we use an entity record previously fetched with explicit optimistic locking?
There are many types of explicit locking options. But, passing the
LockModeType
is the same.I suppose you are talking about the article I wrote about
LockModeType.OPTIMISTIC
.For
LockModeType.OPTIMISTIC
, you’d want to acquire a shared lock on the entity you passed theLockModeType.OPTIMISTIC
prior to existing your method. For other explicit lock mode options, likeOPTIMISTIC_FORCE_INCREMENT
,you don’t need to do that.