Spring Data JPA-悲观锁定不起作用

问题描述

使用:Spring Boot 2.3.3,MysqL 5.7(当前通过TestContainers),JUnit 5

我在Spring MVC应用程序中有一个JpaRepository ,其方法设置为@Lock(LockModeType.pessimistic_WRITE),虽然我确实看到SELECT ... FOR UPDATE出现在结果sql中,但它没有。似乎无能为力。

我将代码放在下面,但是,如果我尝试启动进行相同调用的多个线程,则每个线程都可以读取相同的初始值,似乎没有阻塞/等待的可能。而且我的理解是,任何@Transactional(也来自org.springframework.transaction命名空间)中的所有“额外”调用方法都成为原始事务的一部分。

我不知道自己在做什么错。任何帮助将不胜感激,即使这意味着指出我的理解/期望有缺陷。

存储库

public interface AccountDao extends JpaRepository<Account,Long> {
  @Lock(LockModeType.pessimistic_WRITE)
  public Optional<Account> findById(Long id);
}

服务

帐户服务

@Service
public class AccountServiceImpl implements AccountService {
  @Autowired
  private FeeService feeService;

  @Override
  @Transactional // have also tried this with REQUIRES_NEW,but the same results occur
  public void doTransfer(Long senderId,Long recipientId,TransferDto dto) {
    // do some unrelated stuff

    this.feeService.processFees(recipientId);
  }
}

收费服务

@Service
public class FeeServiceImpl implements FeeService {
  @Autowired
  private AccountDao accountDao;

  @Override
  @Transactional // have also tried removing this
  public void processFees(Long recipientId) {
    // this next line is actually done through another service with a @Transactional annotation,but even without that annotation it still doesn't work
    Account systemAccount = this.accountDao.findById(recipientId);

    System.out.println("System account value: " + systemAccount.getFunds());

    systemAccount.addToFunds(5);

    System.out.println("Saving system account value: " + systemAccount.getFunds());
  }
}

测试

public class TheTest {
  // starts a @SpringBoottest with ```webEnvironment = WebEnvironment.RANDOM_PORT``` so it should start up a dedicated servlet container

  // also auto configures a WebTestClient

  @Test
  @Transactional
  public void testLocking() {
    // inserts a bunch of records to have some users and accounts to test with and does so via JPA,hence the need for @Transactional

    // code here to init an ExecutorService and a synchronized list

    // code here to create a series of threads via the ExecutorService that uses different user IDs as the sender,but the same ID for the recipient,hence the need for pessimistic locking
  }
}

如有必要,我可以输入测试代码,但是我不确定还需要其他哪些细节。

所得到的输出(尤其是从System.out.println中的FeeServiceImpl调用显示,在所有线程中都读取了相同的“系统帐户”值,因此,保存的值也始终是一样。

应用程序启动时,该值为0,并且所有线程都读取该0,而没有明显的锁定或等待。我可以看到有多个事务正在启动和提交(我在Hibernate的TransactionImpl上提高了日志记录级别),但是,这似乎并不重要。

希望我能俯瞰或做些愚蠢的事情,但是我无法弄清楚它是什么。

谢谢!

解决方法

当然,这是我没想到的东西。

奇怪的是,我的表是使用MyISAM而不是InnoDB创建的,因为很长一段时间以来这并不是MySQL中创建表的默认值。

所以,这就是我所做的:

  1. 我以为我正在使用MySQL 8.0。事实证明,当使用未明确命名版本的JDBC连接字符串时,TestContainers的默认值(在我的情况下为5.7.22)。所以我解决了。

  2. 由于MyISAM仍在使用中,因此仍无法解决问题。事实证明这是因为我的配置中有旧版方言设置。将其更新为MySQL57Dialect之类的内容可以纠正此问题。

这实际上也解释了我在JUnit测试中看到的“怪异”行为,因为值立即弹出到DB中而不回滚,等等。

我希望这会在将来对其他人有所帮助!