Spring Boot 使用@version为Hibernate开放式锁定编写@Test未按预期工作

gojuced7  于 2022-11-05  发布在  Spring
关注(0)|答案(3)|浏览(117)

我有简单的UserAccount实体,它们看起来像:

@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;
}

还有:

@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;

}

所以我试着写一个@Test来确定它,它就像这样:

@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);
    }

但是它没有抛出异常,正如我所期望的!!
此外,当我执行accountRepository.save(accountInDeviceOne)时,它不会递增version字段。
而在调试器控制台中,如下图所示,我不知道为什么它们都指向同一个资源!!!

有人能帮助我理解这里出了什么问题吗?我如何为这个乐观的锁定概念编写测试?
任何帮助都将不胜感激!

nukf8bse

nukf8bse1#

您只需在两个并发线程中更新一行,如下所示:

@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class SaveAccountOptimisticallyTest {

@Autowired
private AccountRepository repository;
private Long savedAccountId;

@BeforeEach
public void insertUsers(){
    Account account = Account.builder()
            .balance(BigDecimal.ZERO)
            .build();
    savedAccountId = repository.save(account).getId();

}

@Test
public void test_saving_account_optimistically() throws InterruptedException {
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            Account account = repository.findById(savedAccountId).orElse(null);
            try {
                Thread.sleep(6000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.setBalance(BigDecimal.ONE);
            try {
                repository.save(account);
            }catch (Exception e){
                Assertions.assertEquals(e.getCause(),"StaleObjectStateException");
            }

        }
    });

    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            Account account = repository.findById(savedAccountId).orElse(null);
            account.setBalance(BigDecimal.TEN);
            repository.save(account);
        }
    });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
}

}
请注意,第一个runnable将在从数据库中检索对象6秒后尝试更新获取的对象,在此之前,该对象的版本字段将由其他线程(t2)更新,这将在t1导致OptimisticLock(Hibernate的StaleObjectStateException)异常。

brc7rcf0

brc7rcf02#

单个会话
一级高速缓存:Hibernate一级高速缓存与会话对象相关联。默认情况下,Hibernate一级高速缓存处于启用状态,无法将其禁用。但是,Hibernate提供了一些方法,通过这些方法,我们可以该高速缓存中删除选定的对象或完全清除高速缓存。在会话中高速缓存的任何对象对于其他会话都不可见,并且当会话关闭时,所有高速缓存的对象也将丢失。
因此,当您特灵通过accountRepository.findAccountByUser_Username hib从缓存中找到db对象时,就是这样。

mefy6pfw

mefy6pfw3#

这是因为JPA中的自然行为。因为Hibernate实现了JPA,所以它使用缓存,而且它还有持久性上下文。当对象不在持久性上下文中时,使用Hibernate从数据库中调用(实体)(PC)它从DB加载并放入PC,然后作为调用结果返回。因此,当您从其他位置或以相同的方法再次加载它时,它将从PC返回相同的对象。而且,当您更新对象时,它将更新PC中的相同对象。但是,只有在提交事务时,对对象所做的更改才会刷新到DB。在您的示例中,您将两次从DB获取相同的对象。因此,第一次将对象从DB放入PC并返回,第二次将从PC返回相同的对象,并且不调用DB。您是否具有accountInDeviceOne和accountInDeviceTwo并不重要,因为它们都只是持久上下文(PC0)中对相同对象的引用。因此,它不无论您对哪个引用进行更改,您都是在更改同一个对象。此外,当提交事务时,版本会递增,更改会刷新到DB中。并且,在创建事务的方法完成后,提交事务。如果您希望版本立即递增,则可以通过调用实体管理器中的flush方法或在您使用的SpringDataJPA也有同样的可能性,但这并不是通常的做法。

相关问题