Good morning,
I am trying to understand if Hibernate may do any management when a pessimistic lock is used on an entity when its using the same “persistence context”.
Basically, does Hibernate do any “clever” management to avoid what would be an obvious locking exception?
Example:
- Obtain Lock : Thread 1 :
em.find(User.class,"Alice",LockModeType.PESSIMISTIC_WRITE);
- Obtain Lock : Thread 2 :
em.find(User.class,"Alice",LockModeType.PESSIMISTIC_WRITE);
Will Hibernate not call the actual locking for update
on thread 2 untill the transaction has been comitted for thread/session 1?
I was a bit confused as the docs suggested it should throw a Locking exception:
JPA - LockModeType Docs
A lock with LockModeType.PESSIMISTIC_WRITE can be obtained on an entity instance to force serialization among transactions attempting to update the entity data. A lock with LockModeType.PESSIMISTIC_READ can be used to query data using repeatable-read semantics without the need to reread the data at the end of the transaction to obtain a lock, and without blocking other transactions reading the data. A lock with LockModeType.PESSIMISTIC_WRITE can be used when querying data and there is a high likelihood of deadlock or update failure among concurrent updating transactions.
The persistence implementation must support the use of locks of type LockModeType.PESSIMISTIC_READ and LockModeType.PESSIMISTIC_WRITE with non-versioned entities as well as with versioned entities.
When the lock cannot be obtained, and the database locking failure results in transaction-level rollback, the provider must throw the PessimisticLockException and ensure that the JTA transaction or EntityTransaction has been marked for rollback.
When the lock cannot be obtained, and the database locking failure results in only statement-level rollback, the provider must throw the LockTimeoutException (and must not mark the transaction for rollback).
But when I have written such tests in Spring Boot using @PersistenceContext EntityManager em
and run multiple queries with a pooled thread executor (with some fake “work” during the transaction) and could not cause a PessimisticLockException
to occur, it seemed that it “knew” it would happen and was managed.
I don’t know if I am misunderstanding the implementation but any clarity or links to help would be great!
Thank you.
EDIT: Adding test case:
@SpringBootTest
public class LockingTest {
@Autowired
private TransactionWrapperBean transactionWrapperBean;
@Test
@Sql(statements = "insert into users (id,enabled,user_type) values ('1',1,'CUSTOMER');")
public void expectLockingExceptions() {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executorService.submit(() -> {
try {
transactionWrapperBean.doJob();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
throw new RuntimeException(e);
}
});
}
await().atMost(2, TimeUnit.MINUTES)
.until(() -> invocationCount.get() == 100);
}
}
@Component
public class TransactionWrapperBean {
@PersistenceContext
private EntityManager entityManager;
public static AtomicInteger invocationCount = new AtomicInteger(0);
@Transactional
public void doJob() throws InterruptedException {
System.out.println("Doing Job!");
User user = entityManager.find(User.class, "1", LockModeType.PESSIMISTIC_WRITE);
Assertions.assertNotNull(user);
Thread.sleep(100);
entityManager.persist(user);
System.out.println(invocationCount.incrementAndGet());
}
}
Test output:
Doing Job!
Doing Job!
Doing Job!
Doing Job!
1
Doing Job!
2
Doing Job!
3
Doing Job!
4
Doing Job!
5
Doing Job!
6
Doing Job!
7
Doing Job!
8
....
Doing Job!
2025-01-16T10:09:33.488Z DEBUG 20698 --- [xxxxx] [ool-1-thread-10] org.hibernate.SQL :
select u1_0.id,u1_0.user_type,u1_0.enabled,u1_0.password,u1_0.username from users u1_0 where u1_0.id=? for update
89
Doing Job!
2025-01-16T10:09:33.589Z DEBUG 20698 --- [xxxxx] [pool-1-thread-4] org.hibernate.SQL :
select u1_0.id,u1_0.user_type,u1_0.enabled,u1_0.password,u1_0.username from users u1_0 where u1_0.id=? for update
90
Doing Job!
2025-01-16T10:09:33.691Z DEBUG 20698 --- [xxxxx] [pool-1-thread-9] org.hibernate.SQL :
select u1_0.id,u1_0.user_type,u1_0.enabled,u1_0.password,u1_0.username from users u1_0 where u1_0.id=? for update
91
92
93
94
95
96
97
98
99
100