For this experimental project based on the spring-boot-starter-data-jpa
dependency and H2
in-memory database, I defined a User
entity with two fields (id
and firstName
) and declared a UsersRepository
by extending the CrudRepository
interface.
Now, consider a simple controller which provides two endpoints: /print-user
reads the same user twice with some interval printing out its first name, and /update-user
is used to change the user's first name in between those two reads. Notice that I deliberately set Isolation.READ_COMMITTED
level and expected that during the course of the first transaction, a user which is retrieved twice by the same id will have different names. But instead, the first transaction prints out the same value twice. To make it more clear, this is the complete sequence of actions:
- Initially,
jeremy
's first name is set toJeremy
. - Then I call
/print-user
which prints outJeremy
and goes to sleep. - Next, I call
/update-user
from another session and it changesjeremy
's first name toBob
. - Finally, when the first transaction gets awakened after sleep and re-reads the
jeremy
user, it prints outJeremy
again as his first name even though the first name has already been changed toBob
(and if we open the database console, it's now indeed stored asBob
, notJeremy
).
It seems like setting isolation level has no effect here and I'm curious why this is so.
@RestController
@RequestMapping
public class UsersController {
private final UsersRepository usersRepository;
@Autowired
public UsersController(UsersRepository usersRepository) {
this.usersRepository = usersRepository;
}
@GetMapping("/print-user")
@ResponseStatus(HttpStatus.OK)
@Transactional (isolation = Isolation.READ_COMMITTED)
public void printName() throws InterruptedException {
User user1 = usersRepository.findById("jeremy");
System.out.println(user1.getFirstName());
// allow changing user's name from another
// session by calling /update-user endpoint
Thread.sleep(5000);
User user2 = usersRepository.findById("jeremy");
System.out.println(user2.getFirstName());
}
@GetMapping("/update-user")
@ResponseStatus(HttpStatus.OK)
@Transactional(isolation = Isolation.READ_COMMITTED)
public User changeName() {
User user = usersRepository.findById("jeremy");
user.setFirstName("Bob");
return user;
}
}