Data JPA并发测试:PostgreSQL竞态条件与ACID默认行为探究
问题:Data JPA + PostgreSQL 并发转账的竞态条件行为不符合预期
我想深入了解Data JPA与PostgreSQL在竞态条件和ACID特性方面的默认行为,于是搭建了账户表、JPA实体以及处理转账的AccountService。先通过同步测试验证了环境没问题,接着写了并发测试——我知道PostgreSQL默认隔离级别是READ COMMITTED,只能防止脏读,本以为会出现金额异常(比如凭空增加),但实际测试结果是只有1笔转账生效,最终两个账户余额都是50,和预期的失败情况不一样。
我用Docker部署了最新版PostgreSQL做集成测试环境,相关代码如下:
@Service public class AccountService { @Autowired private AccountRepository accountRepository; @Transactional public void deposit(int from, int to, int amount) { Account f = accountRepository.findByOwner(from).orElseThrow(RuntimeException::new); Account t = accountRepository.findByOwner(to).orElseThrow(RuntimeException::new); if (f.getBalance() >= amount) { f.setBalance(f.getBalance() - amount); t.setBalance(t.getBalance() + amount); accountRepository.save(f); accountRepository.save(t); } } }
@DataJpaTest @Import(AccountService.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) public class ConcurrencyTest { @Autowired private AccountRepository accountRepository; @Autowired private AccountService accountService; @BeforeEach void setUp() { Account from = Account .builder() .owner(1) .balance(100) .build(); Account to = Account .builder() .owner(2) .balance(0) .build(); accountRepository.save(from); accountRepository.save(to); } @Test public void testSync() { accountService.deposit(1, 2, 50); Account f = accountRepository.findByOwner(1).get(); Account t = accountRepository.findByOwner(2).get(); assertEquals(50, f.getBalance()); assertEquals(50, t.getBalance()); } @Test public void concurrencyTest() throws InterruptedException { int NUMBER_OF_THREADS = 5; ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_OF_THREADS); CountDownLatch countDownLatch = new CountDownLatch(NUMBER_OF_THREADS); for (int i = 0; i < NUMBER_OF_THREADS; i++) { executorService.submit(() -> { try { accountService.deposit(1, 2, 50); } catch (Exception e) { e.printStackTrace(); } finally { countDownLatch.countDown(); } }); } countDownLatch.await(); executorService.shutdown(); Account f = accountRepository.findByOwner(1).get(); Account t = accountRepository.findByOwner(2).get(); assertEquals(0, f.getBalance()); // 实际结果为50 assertEquals(100, t.getBalance()); // 实际结果为50 } }
内容的提问来源于stack exchange,提问作者Andrin F.
相关产品推荐
相关产品推荐

