Spring 数据 JPA - 悲观锁定不起作用

Spring Data JPA - Pessimistic Locking Not Working

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

我在 Spring MVC 应用程序中有一个 JpaRepository ,它的方法设置为 @Lock(LockModeType.PESSIMISTIC_WRITE),而我确实看到 SELECT ... FOR UPDATE 出现在结果 SQL,它似乎没有做任何事情。

我将把代码放在下面,但是,如果我尝试启动多个线程进行相同的调用,每个线程都能够读取相同的初始值,而且似乎什么都没有 block/wait.我的理解是,任何“另外”调用的方法也是 @Transactional(来自 org.springframework.transaction 命名空间)是原始事务的一部分。

我不知道我做错了什么。任何帮助将不胜感激,即使这意味着指出我的 understanding/expectations 有缺陷。

存储库

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

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

结果输出(尤其是 FeeServiceImpl 中的 System.out.println 调用)显示所有线程都读入了相同的“系统帐户”值,因此保存的值也是总是一样。

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

希望我忽略了或做了一些愚蠢的事情,但是,我不太明白它是什么。

谢谢!

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

事实证明,我的 table 是使用 MyISAM 而不是 InnoDB 创建的,奇怪的是,因为这在很长一段时间内都不是 MySQL 中 table 创建的默认设置时间.

所以,这就是我所做的:

  1. 我以为我用的是 MySQL 8.0。当使用未具体命名版本的 JDBC 连接字符串时,结果是 TestContainers 默认值(在我的例子中为 5.7.22)。所以我解决了这个问题。

  2. 这仍然没有解决问题,因为 MyISAM 仍在使用。原来这是因为我的配置中有一个遗留方言设置。将其更新为 MySQL57Dialect 更正了这一点。

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

我希望这对以后的其他人有所帮助!