问题描述
使用: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中创建表的默认值。
所以,这就是我所做的:
-
我以为我正在使用MySQL 8.0。事实证明,当使用未明确命名版本的JDBC连接字符串时,TestContainers的默认值(在我的情况下为5.7.22)。所以我解决了。
-
由于MyISAM仍在使用中,因此仍无法解决问题。事实证明这是因为我的配置中有旧版方言设置。将其更新为MySQL57Dialect之类的内容可以纠正此问题。
这实际上也解释了我在JUnit测试中看到的“怪异”行为,因为值立即弹出到DB中而不回滚,等等。
我希望这会在将来对其他人有所帮助!