I have simple User
and Account
entities, they look like:
@Entity
@Table(name="USERS")
public class User {
@NotNull
@Column(name = "USERNAME",length = 50, unique = true)
private String username;
@NotNull
@Column(name = "PASSWORD")
private String password;
@NotNull
@Column(name = "ROLE",length = 20)
@Enumerated(EnumType.STRING)
private Role role;
}
And:
@Entity
@Table(name = "ACCOUNT")
public class Account {
@NotNull
@Column(name = "BALANCE")
private BigDecimal balance;
@JoinColumn(name = "USER_ID")
@OneToOne(targetEntity = User.class, fetch = FetchType.LAZY)
private User user;
@Version
private int version;
}
So I tried to write a @Test
to become sure about it, and it is like:
@Test
public void test_optimistic_locking_concept() {
User user = new User("test", "123456", Role.ROLE_USER);
user = userRepository.save(user);
Account account = new Account();
account.setBalance(new BigDecimal("5000"));
account.setUser(user);
accountRepository.save(account);
// fetching account record for different devices
Account accountInDeviceOne = new Account();
accountInDeviceOne = accountRepository.findAccountByUser_Username(user.getUsername()).get();
Account accountInDeviceTwo = new Account();
accountInDeviceTwo = accountRepository.findAccountByUser_Username(user.getUsername()).get();
// each device tries to change the account balance by debit/credit
accountInDeviceOne.setBalance(accountInDeviceOne.getBalance().subtract(new BigDecimal("1500")));
accountInDeviceTwo.setBalance(accountInDeviceTwo.getBalance().add(new BigDecimal("2500")));
// The versions of the updated accounts are both 0.
Assertions.assertEquals(0, accountInDeviceOne.getVersion());
Assertions.assertEquals(0, accountInDeviceTwo.getVersion());
// first device request update
accountInDeviceOne = accountRepository.save(accountInDeviceOne);
// !Exception!
accountInDeviceTwo = accountRepository.save(accountInDeviceTwo);
}
But it doesn't throw Exception, as I expected!!
Also it does not increment the version
field when I do accountRepository.save(accountInDeviceOne)
.
And in debugger console, as it is shown below, I don't know the reason why they are all pointing to the same resource!!!
Would someone please help me to understand what is going wrong here and how can I write test for this optimistic Locking Concept?
Any help would be appreciated!!
CodePudding user response:
Single session
First Level Cache: Hibernate first level cache is associated with the Session object. Hibernate first level cache is enabled by default and there is no way to disable it. However hibernate provides methods through which we can delete selected objects from the cache or clear the cache completely. Any object cached in a session will not be visible to other sessions and when the session is closed, all the cached objects will also be lost.
So when you tring find db object by accountRepository.findAccountByUser_Username
hibernate get it from cache, that's way.
CodePudding user response:
This is because of natural behaviour in JPA. Since Hibernate implements JPA it uses caching and also it has persistence context. So when you load the object (entity) from DB using hibernate the logic is following. When the object is not in persistent context (PC) it is loaded from DB and put to Pc and returned as the result of the call. So when you load it again from other place or in the same method it will returned the same object from PC. Also when you update the object it will update the same object in PC. But the changes made on object will be flushed to DB only when transaction is committed. In your case you are getting the same object from DB twice. So first time object is put to PC from DB and returned and the second time the same object from PC is returned and no call to DB is made. It doesn't matter you have accountInDeviceOne and accountInDeviceTwo because both of them are just reference to the same object in persistent context (PC0. Therefore it doesn't matter on which reference you will made the changes you are changing the same object. Also version is incremented when transaction is committed and changes are flushed to DB. And the transaction is committed after method who created transaction is done. If you want the version to be incremented immediately you can force changes from PC to be flushed to DB by calling flush method in Entity Manager or in case you are using Spring Data JPA also repositories has the same possibility. But that is not usual practice to do that.