I just want to better understand the default behaviour of data jpa and postgressql regarding race conditions and ACID.
Therefore I create an Account Table and JPA Entity aswell as a Service that manages the account transfers from one user to another.
I first created a synchronous test to test the setup is correct. After that I created a concurrency test where I was expecting it to fail because I don't handle any race conditions and as far as I understand the default ACID behavior of Postgresql is READ COMMITED which only prevents dirty reads. But actually the test fails because it seems that only 1 transaction (deposit) has been registered. So in the end both accounts will have a balance of 50. I was expecting that more money has been created.
I setup an integration test using a docker container with the latest postgresql and Import the AccountService inside the test.
@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()); // result is 50
assertEquals(100, t.getBalance()); // result is 50
}
}
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744709277a4589241.html
评论列表(0条)