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 创建的默认设置时间.
所以,这就是我所做的:
我以为我用的是 MySQL 8.0。当使用未具体命名版本的 JDBC 连接字符串时,结果是 TestContainers 默认值(在我的例子中为 5.7.22)。所以我解决了这个问题。
这仍然没有解决问题,因为 MyISAM 仍在使用。原来这是因为我的配置中有一个遗留方言设置。将其更新为 MySQL57Dialect 更正了这一点。
这实际上也解释了我在 JUnit 测试中看到的“奇怪”行为,因为值会立即弹出到数据库中而不回滚等。
我希望这对以后的其他人有所帮助!
使用: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 创建的默认设置时间.
所以,这就是我所做的:
我以为我用的是 MySQL 8.0。当使用未具体命名版本的 JDBC 连接字符串时,结果是 TestContainers 默认值(在我的例子中为 5.7.22)。所以我解决了这个问题。
这仍然没有解决问题,因为 MyISAM 仍在使用。原来这是因为我的配置中有一个遗留方言设置。将其更新为 MySQL57Dialect 更正了这一点。
这实际上也解释了我在 JUnit 测试中看到的“奇怪”行为,因为值会立即弹出到数据库中而不回滚等。
我希望这对以后的其他人有所帮助!