Spring Data JPA entity locking
Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?
What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?
Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.
So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!
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_READto apply a shared or read lock on the associated table records.LockModeType.PESSIMISTIC_WRITEto apply an exclusive or write lock on the associated table records.
If the underlying database does not support shared locks, then the PESSIMISTIC_READstrategy 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?
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.






