Why does @Transactional isolation level have no effect when updating entities with Spring Data JPA?
There are two issues with your code.
You are performing usersRepository.findById("jeremy");
twice in the same transaction, chances are your second read is retrieving the record from the Cache
. You need to refresh the cache when you read the record for the second time. I have updated code which uses entityManager
, please check how it can be done using the JpaRepository
User user1 = usersRepository.findById("jeremy");
Thread.sleep(5000);
entityManager.refresh(user1);
User user2 = usersRepository.findById("jeremy");
Here are the logs from my test case, please check SQL queries:
- The first read operation is completed. Thread is waiting for the timeout.
Hibernate: select person0_.id as id1_0_0_, person0_.city as city2_0_0_, person0_.name as name3_0_0_ from person person0_ where person0_.id=?
- Triggered update to Bob, it selects and then updates the record.
Hibernate: select person0_.id as id1_0_0_, person0_.city as city2_0_0_, person0_.name as name3_0_0_ from person person0_ where person0_.id=?
Hibernate: update person set city=?, name=? where id=?
- Now thread wakes up from Sleep and triggers the second read. I could not see any DB query triggered i.e the second read is coming from the cache.
The second possible issue is with /update-user
endpoint handler logic. You are changing the name of the user but not persisting it back, merely calling the setter method won't update the database. Hence when other endpoint's Thread
wakes up it prints Jeremy.
Thus you need to call userRepository.saveAndFlush(user)
after changing the name.
@GetMapping("/update-user")
@ResponseStatus(HttpStatus.OK)
@Transactional(isolation = Isolation.READ_COMMITTED)
public User changeName() {
User user = usersRepository.findById("jeremy");
user.setFirstName("Bob");
userRepository.saveAndFlush(user); // call saveAndFlush
return user;
}
Also, you need to check whether the database supports the required isolation level. You can refer H2 Transaction Isolation Levels
Your method to update @GetMapping("/update-user")
is set with an isolation level @Transactional(isolation = Isolation.READ_COMMITTED)
so commit()
step is never reached in this method.
You must change isolation level or read your value in your transaction to commit the changes :)
user.setFirstName("Bob");
does not ensure your data will be committed
Thread Summary will look like this :
A: Read => "Jeremy"
B: Write "Bob" (not committed)
A: Read => "Jeremy"
Commit B : "Bob"
// Now returning "Bob"